From c542e11f631fa1aa4643b4a537ea6fcb26d8ebd4 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Mar 2020 18:34:50 -0400 Subject: [PATCH 1/8] Remove marshmallow from result/ and requirements This commit finishes the process of removing marshmallow from terra. It rebuilds the result class as bare python classes or SimpleNamespace subclasses (for classes that allow arbitrary fields). After the result class has been converted there is nothing using marshmallow or all the support code in qiskit/validation. So this continues the removal process by removing those and removing marshmallow and marshmallow-polyfield from the requirements list. This commit depends on #4027 and #4016 and can't be merged without those. --- qiskit/result/models.py | 192 +++++----- qiskit/result/result.py | 49 ++- qiskit/validation/__init__.py | 1 - qiskit/validation/base.py | 370 ------------------- qiskit/validation/exceptions.py | 12 +- qiskit/validation/fields/__init__.py | 104 ------ qiskit/validation/fields/containers.py | 109 ------ qiskit/validation/fields/custom.py | 207 ----------- qiskit/validation/fields/polymorphic.py | 251 ------------- qiskit/validation/validate.py | 109 ------ requirements.txt | 2 - setup.py | 2 - test/python/validation/__init__.py | 15 - test/python/validation/test_custom_fields.py | 167 --------- test/python/validation/test_fields.py | 152 -------- test/python/validation/test_models.py | 172 --------- test/python/validation/test_schemas.py | 70 ---- test/python/validation/test_validators.py | 78 ---- 18 files changed, 152 insertions(+), 1910 deletions(-) delete mode 100644 qiskit/validation/base.py delete mode 100644 qiskit/validation/fields/__init__.py delete mode 100644 qiskit/validation/fields/containers.py delete mode 100644 qiskit/validation/fields/custom.py delete mode 100644 qiskit/validation/fields/polymorphic.py delete mode 100644 qiskit/validation/validate.py delete mode 100644 test/python/validation/__init__.py delete mode 100644 test/python/validation/test_custom_fields.py delete mode 100644 test/python/validation/test_fields.py delete mode 100644 test/python/validation/test_models.py delete mode 100644 test/python/validation/test_schemas.py delete mode 100644 test/python/validation/test_validators.py diff --git a/qiskit/result/models.py b/qiskit/result/models.py index d3e5cb0fb810..1a1759401cb9 100644 --- a/qiskit/result/models.py +++ b/qiskit/result/models.py @@ -14,88 +14,60 @@ """Schema and helper models for schema-conformant Results.""" -from marshmallow.validate import Length, OneOf, Regexp, Range +import copy +from types import SimpleNamespace -from qiskit.validation.base import BaseModel, BaseSchema, ObjSchema, bind_schema -from qiskit.validation.fields import Complex, ByType -from qiskit.validation.fields import Boolean, DateTime, Integer, List, Nested -from qiskit.validation.fields import Raw, String, NumpyArray -from qiskit.validation.validate import PatternProperties from qiskit.qobj.utils import MeasReturnType, MeasLevel - - -class ExperimentResultDataSchema(BaseSchema): - """Schema for ExperimentResultData.""" - - counts = Nested(ObjSchema, - validate=PatternProperties( - {Regexp('^0x([0-9A-Fa-f])+$'): Integer()})) - snapshots = Nested(ObjSchema) - memory = List(Raw(), - validate=Length(min=1)) - statevector = NumpyArray(Complex(), - validate=Length(min=1)) - unitary = NumpyArray(NumpyArray(Complex(), - validate=Length(min=1)), - validate=Length(min=1)) - - -class ExperimentResultSchema(BaseSchema): - """Schema for ExperimentResult.""" - - # Required fields. - shots = ByType([Integer(), List(Integer(validate=Range(min=1)), - validate=Length(equal=2))], - required=True) - success = Boolean(required=True) - data = Nested(ExperimentResultDataSchema, required=True) - - # Optional fields. - status = String() - seed = Integer() - meas_level = Integer(validate=OneOf(choices=(MeasLevel.RAW, - MeasLevel.KERNELED, - MeasLevel.CLASSIFIED))) - meas_return = String(validate=OneOf(choices=(MeasReturnType.AVERAGE, - MeasReturnType.SINGLE))) - header = Nested(ObjSchema) - - -class ResultSchema(BaseSchema): - """Schema for Result.""" - - # Required fields. - backend_name = String(required=True) - backend_version = String(required=True, - validate=Regexp('[0-9]+.[0-9]+.[0-9]+$')) - qobj_id = String(required=True) - job_id = String(required=True) - success = Boolean(required=True) - results = Nested(ExperimentResultSchema, required=True, many=True) - - # Optional fields. - date = DateTime() - status = String() - header = Nested(ObjSchema) - - -@bind_schema(ExperimentResultDataSchema) -class ExperimentResultData(BaseModel): - """Model for ExperimentResultData. - - Please note that this class only describes the required fields. For the - full description of the model, please check - ``ExperimentResultDataSchema``. - """ - pass - - -@bind_schema(ExperimentResultSchema) -class ExperimentResult(BaseModel): - """Model for ExperimentResult. - - Please note that this class only describes the required fields. For the - full description of the model, please check ``ExperimentResultSchema``. +from qiskit.validation.exceptions import ModelValidationError + + +class ExperimentResultData: + """Class representing experiment result data""" + + def __init__(self, counts=None, snapshots=None, memory=None, + statevector=None, unitary=None): + """Initialize an ExperimentalResult Data class + + Args: + counts (dict): A dictionary where the keys are the result in + hexadecimal as string of the format "0xff" and the value + is the number of counts for that result + snapshots (dict): A dictionary where the key is the snapshot + slot and the value is a dictionary of the snapshots for + that slot. + memory (list) + statevector (list or numpy.array): A list or numpy array of the + statevector result + unitary (list or numpy.array): A list or numpy arrray of the + unitary result + """ + + if counts is not None: + self.counts = counts + if snapshots is not None: + self.snapshots = snapshots + if memory is not None: + self.memory = memory + if statevector is not None: + self.statevector = statevector + if unitary is not None: + self.unitary = unitary + + def to_dict(self): + out_dict = {} + for field in ['counts', 'snapshots', 'memory', 'statevector', + 'unitary']: + if hasattr(self, field): + out_dict[field] = getattr(self, field) + return out_dict + + @classmethod + def from_dict(cls, data): + return cls(**data) + + +class ExperimentResult(SimpleNamespace): + """Class representing an Experiment Result. Attributes: shots (int or tuple): the starting and ending shot for this data. @@ -104,10 +76,64 @@ class ExperimentResult(BaseModel): meas_level (int): Measurement result level. """ - def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, **kwargs): + def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, + status=None, seed=None, meas_return=None, header=None, + **kwargs): + """Initialize an ExperimentResult object. + + Args: + shots(int or tuple): if an integer the number of shots or if a + tuple the starting and ending shot for this data + success (bool): True if the experiment was successful + data (ExperimentResultData): The data for the experiment's + result + meas_level (int): Measurement result level + status (str): The status of the experiment + seed (int): The seed used for simulation (if run on a simulator) + meas_return (str): The type of measurement returned + header (dict): A free form dictionary header for the experiment + kwargs: Arbitrary extra fields + + Raises: + QiskitError: If meas_return or meas_level are not valid values + """ self.shots = shots self.success = success self.data = data self.meas_level = meas_level - - super().__init__(**kwargs) + if status is not None: + self.status = status + if seed is not None: + self.seed = seed + if meas_return is not None: + if meas_return not in list(MeasReturnType): + raise ModelValidationError('%s not a valid meas_return value') + self.meas_return = meas_return + self.__dict__.update(kwargs) + + def to_dict(self): + out_dict = { + 'shots': self.shots, + 'success': self.success, + 'data': self.data.to_dict(), + 'meas_level': self.meas_level, + } + for field in self.__dict__.keys(): + if field not in ['shots', 'success', 'data', 'meas_level']: + out_dict[field] = getattr(self, field) + return out_dict + + @classmethod + def from_dict(cls, data): + in_data = copy.copy() + in_data['data'] = ExperimentResultData.from_dict(in_data.pop('data')) + return cls(**in_data) + + def __getstate__(self): + return self.to_dict() + + def __setstate__(self, state): + return self.from_dict(state) + + def __reduce__(self): + return (self.__class__, (self.shots, self.success, self.data)) diff --git a/qiskit/result/result.py b/qiskit/result/result.py index 2650ba2c00a6..bd73bb2c5866 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -14,19 +14,19 @@ """Model for schema-conformant Results.""" +import copy +from types import SimpleNamespace + from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.pulse.schedule import Schedule from qiskit.exceptions import QiskitError from qiskit.quantum_info.states import state_to_counts - -from qiskit.validation.base import BaseModel, bind_schema +from qiskit.result.models import ExperimentResult from qiskit.result import postprocess from qiskit.qobj.utils import MeasLevel -from .models import ResultSchema -@bind_schema(ResultSchema) -class Result(BaseModel): +class Result(SimpleNamespace): """Model for Results. Please note that this class only describes the required fields. For the @@ -44,15 +44,48 @@ class Result(BaseModel): """ def __init__(self, backend_name, backend_version, qobj_id, job_id, success, - results, **kwargs): + results, date=None, status=None, header=None, date=None, + status=None, header=None, **kwargs): self.backend_name = backend_name self.backend_version = backend_version self.qobj_id = qobj_id self.job_id = job_id self.success = success self.results = results - - super().__init__(**kwargs) + self.__dict__.update(kwargs) + + def to_dict(self): + out_dict = { + 'backend_name': self.backend_name, + 'backend_version': self.backend_version, + 'qobj_id': self.qobj_id, + 'job_id': self.job_id, + 'success': self.success, + 'results': [x.to_dict() for x in self.results] + } + for field in self.__dict__.keys(): + if field not in ['backend_name', 'backend_version', 'qobj_id', + 'job_id', 'success', 'results']: + out_dict[field] = getattr(self, field) + return out_dict + + @classmethod + def from_dict(cls, data): + in_data = copy.copy() + in_data['results'] = [ + ExperimentResult.from_dict(x) for x in in_data.pop('results')] + cls(**in_data) + + def __getstate__(self): + return self.to_dict() + + def __setstate__(self, state): + return self.from_dict(state) + + def __reduce__(self): + return (self.__class__, (self.backend_name, self.backend_version, + self.qobj_id, self.job_id, self.success, + self.results)) def data(self, experiment=None): """Get the raw data for an experiment. diff --git a/qiskit/validation/__init__.py b/qiskit/validation/__init__.py index 3d08b34fe0ec..022f097690e7 100644 --- a/qiskit/validation/__init__.py +++ b/qiskit/validation/__init__.py @@ -39,5 +39,4 @@ ModelValidationError """ -from .base import BaseModel, BaseSchema, bind_schema, ModelTypeValidator from .exceptions import ModelValidationError diff --git a/qiskit/validation/base.py b/qiskit/validation/base.py deleted file mode 100644 index 025616ff3b09..000000000000 --- a/qiskit/validation/base.py +++ /dev/null @@ -1,370 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Building blocks for Qiskit validated classes. - -This module provides the ``BaseSchema`` and ``BaseModel`` classes as the main -building blocks for defining objects (Models) that conform to a specification -(Schema) and are validated at instantiation, along with providing facilities -for being serialized and deserialized. - -Implementors are recommended to subclass the two classes, and "binding" them -together by using ``bind_schema``:: - - class PersonSchema(BaseSchema): - name = String(required=True) - - @bind_schema(PersonSchema) - class Person(BaseModel): - pass -""" - -from functools import wraps -from types import SimpleNamespace, MethodType - -from marshmallow import ValidationError -from marshmallow import Schema, post_dump, post_load -from marshmallow import fields as _fields -from marshmallow.utils import is_collection, INCLUDE - -from .exceptions import ModelValidationError - - -class ModelTypeValidator(_fields.Field): - """A field able to validate the correct type of a value.""" - - valid_types = (object, ) - - def _expected_types(self): - return self.valid_types - - def check_type(self, value, attr, data, **_): - """Validates a value against the correct type of the field. - - It calls ``_expected_types`` to get a list of valid types. - - Subclasses can do one of the following: - - 1. Override the ``valid_types`` property with a tuple with the expected - types for this field. - - 2. Override the ``_expected_types`` method to return a tuple of - expected types for the field. - - 3. Change ``check_type`` completely to customize validation. - - Note: - This method or the overrides must return the ``value`` parameter - untouched. - """ - expected_types = self._expected_types() - if not isinstance(value, expected_types): - raise self._not_expected_type( - value, expected_types, fields=[self], field_names=attr, data=data) - return value - - @staticmethod - def _not_expected_type(value, type_, **kwargs): - if is_collection(type_) and len(type_) == 1: - type_ = type_[0] - - if is_collection(type_): - body = 'is none of the expected types {}'.format(type_) - else: - body = 'is not the expected type {}'.format(type_) - - message = 'Value \'{}\' {}: {}'.format(value, type(value), body) - return ValidationError(message, **kwargs) - - def make_error_serialize(self, key, **kwargs): - """Helper method to return a ValidationError from _serialize. - - This method wraps the result of ``make_error()``, adding contextual - information in order to provide more informative information to users. - - Args: - key (str): error key index. - **kwargs: additional arguments to ``make_error()``. - - Returns: - ValidationError: an exception with the field name. - """ - bare_error = self.make_error(key, **kwargs) - return ValidationError({self.name: bare_error.messages}, - field_name=self.name) - - -class BaseSchema(Schema): - """Base class for Schemas for validated Qiskit classes. - - Provides convenience functionality for the Qiskit common use case: - - * deserialization into class instances instead of dicts. - * handling of unknown attributes not defined in the schema. - - Attributes: - model_cls (type): class used to instantiate the instance. The - constructor is passed all named parameters from deserialization. - """ - - class Meta: - """Add extra fields to the schema.""" - unknown = INCLUDE - - model_cls = SimpleNamespace - - @post_dump(pass_original=True, pass_many=True) - def dump_additional_data(self, valid_data, original_data, **kwargs): - """Include unknown fields after dumping. - - Unknown fields are added with no processing at all. - - Args: - valid_data (dict or list): data collected and returned by ``dump()``. - original_data (object or list): object passed to ``dump()`` in the - first place. - **kwargs: extra arguments from the decorators. - - Returns: - dict: the same ``valid_data`` extended with the unknown attributes. - - Inspired by https://github.com/marshmallow-code/marshmallow/pull/595. - """ - if kwargs.get('many'): - for i, _ in enumerate(valid_data): - additional_keys = set(original_data[i].__dict__) - set(valid_data[i]) - for key in additional_keys: - if key.startswith('_'): - continue - valid_data[i][key] = getattr(original_data[i], key) - else: - additional_keys = set(original_data.__dict__) - set(valid_data) - for key in additional_keys: - if key.startswith('_'): - continue - valid_data[key] = getattr(original_data, key) - - return valid_data - - @post_load - def make_model(self, data, **_): - """Make ``load`` return a ``model_cls`` instance instead of a dict.""" - return self.model_cls(**data) - - -class _SchemaBinder: - """Helper class for the parametrized decorator ``bind_schema``.""" - - def __init__(self, schema_cls, **kwargs): - """Get the schema for the decorated model.""" - self._schema_cls = schema_cls - self._kwargs = kwargs - - def __call__(self, model_cls): - """Augment the model class with the validation API. - - See the docs for ``bind_schema`` for further information. - """ - # Check for double binding of schemas. - if self._schema_cls.__dict__.get('model_cls', None) is not None: - raise ValueError( - 'The schema {} can not be bound twice. It is already bound to ' - '{}. If you want to reuse the schema, use ' - 'subclassing'.format(self._schema_cls, self._schema_cls.model_cls)) - - # Set a reference to the Model in the Schema, and vice versa. - self._schema_cls.model_cls = model_cls - model_cls.schema = self._schema_cls(**self._kwargs) - - # Append the methods to the Model class. - model_cls.__init__ = self._validate_after_init(model_cls.__init__) - - # Add a Schema that performs minimal validation to the Model. - model_cls.shallow_schema = self._create_validation_schema(self._schema_cls) - - return model_cls - - @staticmethod - def _create_validation_schema(schema_cls, **kwargs): - """Create a patched Schema for validating models. - - Model validation is not part of Marshmallow. Schemas have a ``validate`` - method but this delegates execution on ``load``. Similarly, ``load`` - will call ``_deserialize`` on every field in the schema. - - This function patches the ``_deserialize`` instance method of each - field to make it call a custom defined method ``check_type`` - provided by Qiskit in the different fields at - ``qiskit.validation.fields``. - - Returns: - BaseSchema: a copy of the original Schema, overriding the - ``_deserialize()`` call of its fields. - """ - validation_schema = schema_cls(**kwargs) - for _, field in validation_schema.fields.items(): - if isinstance(field, ModelTypeValidator): - validate_function = field.__class__.check_type - field._deserialize = MethodType(validate_function, field) - - return validation_schema - - @staticmethod - def _validate_after_init(init_method): - """Add validation during instantiation. - - The validation is performed depending on the ``validate`` parameter - passed to the ``init_method``. If ``False``, the validation will not be - performed. - """ - @wraps(init_method) - def _decorated(self, **kwargs): - # Extract the 'validate' parameter. - do_validation = kwargs.pop('validate', True) - if do_validation: - try: - _ = self.shallow_schema._do_load(kwargs, postprocess=False) - except ValidationError as ex: - raise ModelValidationError( - ex.messages, ex.field_name, ex.data, ex.valid_data, **ex.kwargs) from None - - # Set the 'validate' parameter to False, assuming that if a - # subclass has been validated, it superclasses will also be valid. - return init_method(self, **kwargs, validate=False) - - return _decorated - - -def bind_schema(schema, **kwargs): - """Class decorator for adding schema validation to its instances. - - The decorator acts on the model class by adding: - - * a class attribute ``schema`` with the schema used for validation - * a class attribute ``shallow_schema`` used for validation during - instantiation. - - The same schema cannot be bound more than once. If you need to reuse a - schema for a different class, create a new schema subclassing the one you - want to reuse and leave the new empty:: - - class MySchema(BaseSchema): - title = String() - - class AnotherSchema(MySchema): - pass - - @bind_schema(MySchema): - class MyModel(BaseModel): - pass - - @bind_schema(AnotherSchema): - class AnotherModel(BaseModel): - pass - - Note: - By default, models decorated with this decorator are validated during - instantiation. If ``validate=False`` is passed to the constructor, this - validation will not be performed. - - Args: - schema (class): the schema class used for validation. - **kwargs: Additional attributes for the ``marshmallow.Schema`` - initializer. - - Raises: - ValueError: when trying to bind the same schema more than once. - - Return: - type: the same class with validation capabilities. - """ - return _SchemaBinder(schema, **kwargs) - - -def _base_model_from_kwargs(cls, kwargs): - """Helper for BaseModel.__reduce__, expanding kwargs.""" - return cls(**kwargs) - - -class BaseModel(SimpleNamespace): - """Base class for Models for validated Qiskit classes.""" - - def __init__(self, validate=True, **kwargs): - """BaseModel initializer. - - Note: - The ``validate`` argument is used for controlling the behavior of - the schema binding, and will not be present on the created object. - """ - # pylint: disable=unused-argument - super().__init__(**kwargs) - - def __reduce__(self): - """Custom __reduce__ for allowing pickling and unpickling. - - Customize the reduction in order to allow serialization, as the - BaseModels need to be pickled during the use of futures by the backends. - Instead of returning the class, a helper is used in order to pass the - arguments as **kwargs, as it is needed by SimpleNamespace and the - standard __reduce__ only allows passing args as a tuple. - """ - return _base_model_from_kwargs, (self.__class__, self.__dict__) - - def __contains__(self, item): - """Custom implementation of membership test. - - Implement the ``__contains__`` method for catering to the common case - of finding out if a model contains a certain key (``key in model``). - """ - return item in self.__dict__ - - def to_dict(self): - """Serialize the model into a Python dict of simple types. - - Note that this method requires that the model is bound with - ``@bind_schema``. - """ - try: - data = self.schema.dump(self) - except ValidationError as ex: - raise ModelValidationError( - ex.messages, ex.field_name, ex.data, ex.valid_data, **ex.kwargs) from None - - return data - - @classmethod - def from_dict(cls, dict_): - """Deserialize a dict of simple types into an instance of this class. - - Note that this method requires that the model is bound with - ``@bind_schema``. - """ - try: - data = cls.schema.load(dict_) - except ValidationError as ex: - raise ModelValidationError( - ex.messages, ex.field_name, ex.data, ex.valid_data, **ex.kwargs) from None - - return data - - -class ObjSchema(BaseSchema): - """Generic object schema.""" - pass - - -@bind_schema(ObjSchema) -class Obj(BaseModel): - """Generic object in a Model.""" - pass diff --git a/qiskit/validation/exceptions.py b/qiskit/validation/exceptions.py index 44ddd0bd05dc..3fc3b2719a5d 100644 --- a/qiskit/validation/exceptions.py +++ b/qiskit/validation/exceptions.py @@ -15,17 +15,9 @@ """Exceptions for errors raised by the validation.""" -from marshmallow import ValidationError from qiskit.exceptions import QiskitError -class ModelValidationError(QiskitError, ValidationError): +class ModelValidationError(QiskitError): """Raised when a sequence subscript is out of range.""" - def __init__(self, message, field_name=None, data=None, valid_data=None, - **kwargs): - # pylint: disable=super-init-not-called - # ValidationError.__init__ is called manually instead of calling super, - # as the signatures of ValidationError and QiskitError constructors - # differ. - ValidationError.__init__(self, message, field_name, data, valid_data, **kwargs) - self.message = str(message) + pass diff --git a/qiskit/validation/fields/__init__.py b/qiskit/validation/fields/__init__.py deleted file mode 100644 index b1e2a4076a65..000000000000 --- a/qiskit/validation/fields/__init__.py +++ /dev/null @@ -1,104 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Fields to be used with Qiskit validated classes. - -When extending this module with new Fields: - - 1. Distinguish a new type, like the ``Complex`` number in this module. - 2. Use a new Marshmallow field not used in ``qiskit`` yet. - -Marshmallow fields does not allow model validation so you need to create a new -field, make it subclass of the Marshmallow field *and* ``ModelTypeValidator``, -and redefine ``valid_types`` to be the list of valid types. Usually, **the -same types this field deserializes to**. For instance:: - - class Boolean(marshmallow.fields.Boolean, ModelTypeValidator): - __doc__ = _fields.Boolean.__doc__ - - valid_types = (bool, ) - -See ``ModelTypeValidator`` for more subclassing options. -""" - -from datetime import date, datetime - -from marshmallow import fields as _fields - -from qiskit.validation import ModelTypeValidator -from qiskit.validation.fields.polymorphic import ByAttribute, ByType, TryFrom -from qiskit.validation.fields.containers import Nested, List, Dict, NumpyArray - -from .custom import Complex, InstructionParameter, DictParameters - - -class String(_fields.String, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.String.__doc__ - - valid_types = (str, ) - - -class Date(_fields.Date, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.Date.__doc__ - - valid_types = (date, ) - - -class DateTime(_fields.DateTime, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.DateTime.__doc__ - - valid_types = (datetime, ) - - -class Email(_fields.Email, String): - # pylint: disable=missing-docstring - __doc__ = _fields.Email.__doc__ - - -class Url(_fields.Url, String): - # pylint: disable=missing-docstring - __doc__ = _fields.Url.__doc__ - - -class Number(_fields.Number, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.Number.__doc__ - - def _expected_types(self): - return self.num_type - - -class Integer(_fields.Integer, Number): - # pylint: disable=missing-docstring - __doc__ = _fields.Integer.__doc__ - - -class Float(_fields.Float, Number): - # pylint: disable=missing-docstring - __doc__ = _fields.Float.__doc__ - - -class Boolean(_fields.Boolean, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.Boolean.__doc__ - - valid_types = (bool, ) - - -class Raw(_fields.Raw, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.Boolean.__doc__ diff --git a/qiskit/validation/fields/containers.py b/qiskit/validation/fields/containers.py deleted file mode 100644 index aa4dd3105185..000000000000 --- a/qiskit/validation/fields/containers.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Container fields that represent nested/collections of schemas or types.""" - -from collections.abc import Iterable, Mapping - -import numpy as np - -from marshmallow import fields as _fields -from marshmallow.exceptions import ValidationError -from marshmallow.utils import is_collection - -from qiskit.validation import ModelTypeValidator - - -class Nested(_fields.Nested, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.Nested.__doc__ - - def _expected_types(self): - return self.schema.model_cls - - def check_type(self, value, attr, data, **kwargs): - """Validate if the value is of the type of the schema's model. - - Assumes the nested schema is a ``BaseSchema``. - """ - if self.many and not is_collection(value): - raise self._not_expected_type( - value, Iterable, fields=[self], field_names=attr, data=data) - - _check_type = super().check_type - - errors = [] - values = value if self.many else [value] - for idx, v in enumerate(values): - try: - _check_type(v, idx, values, **kwargs) - except ValidationError as err: - errors.append(err.messages) - - if errors: - errors = errors if self.many else errors[0] - raise ValidationError(errors) - - return value - - -class List(_fields.List, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.List.__doc__ - - valid_types = (Iterable, ) - - def check_type(self, value, attr, data, **kwargs): - """Validate if it's a list of valid item-field values. - - Check if each element in the list can be validated by the item-field - passed during construction. - """ - super().check_type(value, attr, data, **kwargs) - - errors = [] - for idx, v in enumerate(value): - try: - self.inner.check_type(v, idx, value, **kwargs) - except ValidationError as err: - errors.append(err.messages) - - if errors: - raise ValidationError(errors) - - return value - - -class Dict(_fields.Dict, ModelTypeValidator): - # pylint: disable=missing-docstring - __doc__ = _fields.Dict.__doc__ - - valid_types = (Mapping, ) - - -class NumpyArray(List): - # pylint: disable=missing-docstring - __doc__ = List.__doc__ - - def _deserialize(self, value, attr, data, **kwargs): - # If an numpy array just return that: - if isinstance(value, np.ndarray): - return value - # If not a native numpy array deserialize the list and convert: - deserialized_list = super(NumpyArray, self)._deserialize( - value, attr, data, **kwargs) - try: - return np.array(deserialized_list) - except ValueError as err: - raise ValidationError([err]) diff --git a/qiskit/validation/fields/custom.py b/qiskit/validation/fields/custom.py deleted file mode 100644 index 516e22a0dbcc..000000000000 --- a/qiskit/validation/fields/custom.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Fields custom to Terra to be used with Qiskit validated classes.""" - -from collections.abc import Mapping - -import numpy - -from marshmallow.utils import is_collection -from marshmallow.exceptions import ValidationError - -from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.validation import ModelTypeValidator - - -class Complex(ModelTypeValidator): - """Field for complex numbers. - - Field for parsing complex numbers: - * deserializes from either a Python `complex` or 2-element collection. - * serializes to a tuple of 2 decimals `(real, imaginary)` - """ - - valid_types = (complex, ) - - default_error_messages = { - 'invalid': '{input} cannot be parsed as a complex number.', - 'format': '"{input}" cannot be formatted as complex number.', - } - - def _serialize(self, value, attr, obj, **_): - try: - return [value.real, value.imag] - except AttributeError: - raise self.make_error_serialize('format', input=value) - - def _deserialize(self, value, attr, data, **_): - if is_collection(value) and (len(value) == 2): - try: - return complex(value[0], value[1]) - except (ValueError, TypeError): - raise self.make_error('invalid', input=value) - elif isinstance(value, complex): - return value - - raise self.make_error('invalid', input=value) - - -class InstructionParameter(ModelTypeValidator): - """Field for objects used in instruction parameters. - - This field provides support for parsing objects of types that uses by - qobj.experiments.instructions.parameters: - * basic Python types: complex, int, float, str, list - * ``numpy``: integer, float, ndarray - - Note that by using this field, serialization-deserialization round-tripping - becomes not possible, as certain types serialize to the same Python basic - type (for example, numpy.float and regular float). If possible, it is - recommended that more specific and defined fields are used instead. - """ - valid_types = (complex, int, float, str, - ParameterExpression, - numpy.integer, numpy.float, - list, numpy.ndarray) - - default_error_messages = { - 'invalid': '{input} cannot be parsed as a parameter.', - 'format': '"{input}" cannot be formatted as a parameter.' - } - - def _serialize(self, value, attr, obj, **kwargs): - # pylint: disable=too-many-return-statements - if is_collection(value): - return [self._serialize(item, attr, obj, **kwargs) for item in value] - - if isinstance(value, complex): - return [value.real, value.imag] - if isinstance(value, numpy.integer): - return int(value) - if isinstance(value, numpy.float): - return float(value) - if isinstance(value, (float, int, str)): - return value - if isinstance(value, ParameterExpression): - if value.parameters: - raise self.make_error_serialize('invalid', input=value) - return float(value) - - # Fallback for attempting serialization. - if hasattr(value, 'to_dict'): - return value.to_dict() - - raise self.make_error_serialize('format', input=value) - - def _deserialize(self, value, attr, data, **kwargs): - if is_collection(value): - return [self._deserialize(item, attr, data, **kwargs) for item in value] - - if isinstance(value, (float, int, str)): - return value - - raise self.make_error('invalid', input=value) - - def check_type(self, value, attr, data, **kwargs): - """Customize check_type for handling containers.""" - # Check the type in the standard way first, in order to fail quickly - # in case of invalid values. - root_value = super().check_type(value, attr, data, **kwargs) - - if is_collection(value): - _ = [super(InstructionParameter, self).check_type(item, attr, data, **kwargs) - for item in value] - - return root_value - - -class DictParameters(ModelTypeValidator): - """Field for objects used in measurement kernel and discriminator parameters. - """ - default_error_messages = { - 'invalid_mapping': 'Not a valid mapping type.', - 'invalid': '{input} cannot be parsed as a parameter.' - } - - def __init__(self, valid_value_types, **kwargs): - """Create new model. - - Args: - valid_value_types (tuple): valid types as values. - """ - # pylint: disable=missing-param-doc - - super().__init__(**kwargs) - self.valid_value_types = valid_value_types - - def _expected_types(self): - return self.valid_value_types - - def check_type(self, value, attr, data, **kwargs): - if value is None: - return None - - _check_type = super().check_type - - errors = [] - if not isinstance(data[attr], Mapping): - raise self.make_error('invalid_mapping') - - try: - if isinstance(value, Mapping): - for v in value.values(): - self.check_type(v, attr, data, **kwargs) - elif is_collection(value): - for v in value: - self.check_type(v, attr, data, **kwargs) - else: - _check_type(value, attr, data, **kwargs) - except ValidationError as err: - errors.append(err.messages) - - if errors: - raise ValidationError(errors) - - return value - - def _validate_values(self, value): - if value is None: - return None - if isinstance(value, complex): - return [value.real, value.imag] - if isinstance(value, self.valid_value_types): - return value - if is_collection(value): - return [self._validate_values(each) for each in value] - if isinstance(value, Mapping): - return {str(k): self._validate_values(v) for k, v in value.items()} - - raise self.make_error('invalid', input=value) - - def _serialize(self, value, attr, obj, **_): - if value is None: - return None - if isinstance(value, Mapping): - return {str(k): self._validate_values(v) for k, v in value.items()} - - raise self.make_error_serialize('invalid_mapping') - - def _deserialize(self, value, attr, data, **_): - if value is None: - return None - if isinstance(value, Mapping): - return value - - raise self.make_error('invalid_mapping') diff --git a/qiskit/validation/fields/polymorphic.py b/qiskit/validation/fields/polymorphic.py deleted file mode 100644 index 538c75f6a850..000000000000 --- a/qiskit/validation/fields/polymorphic.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Polymorphic fields that represent one of several schemas or types.""" - -from collections.abc import Iterable -from functools import partial - -from marshmallow.exceptions import ValidationError -from marshmallow.utils import is_collection -from marshmallow_polyfield import PolyField - -from qiskit.validation import ModelTypeValidator - - -class BasePolyField(PolyField, ModelTypeValidator): - """Base class for polymorphic fields. - - Defines a Field that can contain data fitting different ``BaseSchema``. - Deciding the type is performed by the ``to_dict_selector()`` and - ``from_dict_selector()`` functions, that act on ``choices``. - - Subclasses are recommended to customize the ``to_dict_selector()`` and - ``from_dict_selector()``, adding the necessary logic for inspecting - ``choices`` and the data, and returning one of the Schemas. - - Args: - choices (dict or iterable): iterable or dict containing the schema - instances and the information needed for performing disambiguation. - many (bool): whether the field is a collection of objects. - metadata (dict): the same keyword arguments that ``PolyField`` receives. - """ - - def __init__(self, choices, many=False, **metadata): - - if isinstance(choices, dict): - self._choices = choices.values() - elif isinstance(choices, (list, tuple)): - self._choices = list(choices) - else: - raise ValueError( - '`choices` parameter must be a dict, a list or a tuple') - - to_dict_selector = partial(self.to_dict_selector, choices) - from_dict_selector = partial(self.from_dict_selector, choices) - - super().__init__(to_dict_selector, from_dict_selector, many=many, **metadata) - - def to_dict_selector(self, choices, *args, **_): - """Return an schema in ``choices`` for serialization.""" - raise NotImplementedError - - def from_dict_selector(self, choices, *args, **_): - """Return an schema in ``choices`` for deserialization.""" - raise NotImplementedError - - def _deserialize(self, value, attr, data, **kwargs): - """Override ``_deserialize`` for customizing the exception raised.""" - # pylint: disable=arguments-differ - try: - return super()._deserialize(value, attr, data, **kwargs) - except ValidationError as ex: - if 'deserialization_schema_selector' in ex.messages[0]: - ex.messages[0] = 'Cannot find a valid schema among the choices' - raise - - def _serialize(self, value, key, obj, **kwargs): - """Override ``_serialize`` for customizing the exception raised.""" - try: - return super()._serialize(value, key, obj, **kwargs) - except TypeError as ex: - if 'serialization_schema_selector' in str(ex): - raise ValidationError('Data from an invalid schema') - raise - - def _expected_types(self): - return tuple(schema.model_cls for schema in self._choices) - - def check_type(self, value, attr, data, **kwargs): - """Check if the type of the value is one of the possible choices. - - Possible choices are the model classes bound to the possible schemas. - """ - if self.many and not is_collection(value): - raise self._not_expected_type( - value, Iterable, fields=[self], field_names=attr, data=data) - - _check_type = super().check_type - - errors = [] - values = value if self.many else [value] - for idx, v in enumerate(values): - try: - _check_type(v, idx, values, **kwargs) - except ValidationError as err: - errors.append(err.messages) - - if errors: - errors = errors if self.many else errors[0] - raise ValidationError(errors) - - return value - - -class TryFrom(BasePolyField): - """Polymorphic field that returns the first candidate schema that matches. - - Polymorphic field that accepts a list of candidate schemas, and iterates - through them, returning the first Schema that matches the data. Note that - the list of choices is traversed in order, and an attempt to match the - data is performed until a valid Schema is found, which might have - performance implications. - - Examples: - class PetOwnerSchema(BaseSchema): - pet = TryFrom([CatSchema, DogSchema]) - - Args: - choices (list[class]): list of BaseSchema classes that are iterated in - order for for performing disambiguation. - many (bool): whether the field is a collection of objects. - metadata (dict): the same keyword arguments that ``PolyField`` receives. - """ - - def to_dict_selector(self, choices, base_object, *_): - # pylint: disable=arguments-differ - if getattr(base_object, 'schema'): - if base_object.schema.__class__ in choices: - return base_object.schema - - return None - - def from_dict_selector(self, choices, base_dict, *_): - # pylint: disable=arguments-differ - for schema_cls in choices: - try: - schema = schema_cls() - schema.load(base_dict) - - return schema_cls() - except ValidationError: - pass - return None - - -class ByAttribute(BasePolyField): - """Polymorphic field that disambiguates based on an attribute's existence. - - Polymorphic field that accepts a dictionary of (``'attribute': schema``) - entries, and checks for the existence of ``attribute`` in the data for - disambiguating. - - Examples: - class PetOwnerSchema(BaseSchema): - pet = ByAttribute({'fur_density': CatSchema, - 'barking_power': DogSchema)} - - Args: - choices (dict[string: class]): dictionary with attribute names as - keys, and BaseSchema classes as values. - many (bool): whether the field is a collection of objects. - metadata (dict): the same keyword arguments that ``PolyField`` receives. - """ - - def to_dict_selector(self, choices, base_object, *_): - # pylint: disable=arguments-differ - if getattr(base_object, 'schema'): - if base_object.schema.__class__ in choices.values(): - return base_object.schema - - return None - - def from_dict_selector(self, choices, base_dict, *_): - # pylint: disable=arguments-differ - for attribute, schema_cls in choices.items(): - if attribute in base_dict: - return schema_cls() - - return None - - -class ByType(ModelTypeValidator): - """Polymorphic field that disambiguates based on an attribute's type. - - Polymorphic field that accepts a list of ``Fields``, and checks that the - data belongs to any of those types. Note this Field does not inherit from - ``BasePolyField``, as it operates directly on ``Fields`` instead of - operating in ``Schemas``. - - Examples: - class PetOwnerSchema(BaseSchema): - contact_method = ByType([fields.Email(), fields.Url()]) - - Args: - choices (list[Field]): list of accepted `Fields` instances. - *args: args for Field. - **kwargs: kwargs for Field. - """ - - default_error_messages = { - 'invalid': 'Value {value} does not fit any of the types {types}.' - } - - def __init__(self, choices, *args, **kwargs): - self.choices = choices - super().__init__(*args, **kwargs) - - def _serialize(self, value, attr, obj, **kwargs): - for field in self.choices: - try: - return field._serialize(value, attr, obj, **kwargs) - except (ValidationError, ValueError): - pass - - raise self.make_error_serialize('invalid', value=value, types=self.choices) - - def _deserialize(self, value, attr, data, **kwargs): - for field in self.choices: - try: - return field._deserialize(value, attr, data, **kwargs) - except (ValidationError, ValueError): - pass - - raise self.make_error('invalid', value=value, types=self.choices) - - def check_type(self, value, attr, data, **kwargs): - """Check if at least one of the possible choices validates the value. - - Possible choices are assumed to be ``ModelTypeValidator`` fields. - """ - for field in self.choices: - if isinstance(field, ModelTypeValidator): - try: - return field.check_type(value, attr, data, **kwargs) - except ValidationError: - pass - - raise self._not_expected_type( - value, [field.__class__ for field in self.choices], - fields=[self], field_names=attr, data=data) diff --git a/qiskit/validation/validate.py b/qiskit/validation/validate.py deleted file mode 100644 index ad0c7f6a75a5..000000000000 --- a/qiskit/validation/validate.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Validators for Qiskit validated classes.""" - -from collections.abc import Mapping - -from marshmallow import ValidationError -from marshmallow.validate import Validator - - -class Or(Validator): - """Validate using a boolean "or" against a list of Validators. - - This validator accepts a list of other ``Validators``, and returns True if - any of those validators pass. - - Examples: - - wheels = Integer(validate=Or(Equal(4), Equal(2))) - """ - - def __init__(self, validators): - """Or initializer. - - Args: - validators (list[Validator]): list of Validators. - """ - self.validators = validators - - def __call__(self, value): - for validator in self.validators: - try: - return validator(value) - except ValidationError: - pass - - raise ValidationError('Data could not be validated against any ' - 'validator') - - -class PatternProperties(Validator): - """Validate the keys and values of an object, disallowing additional ones. - - This validator is a combination of the jsonschema `patternProperties` and - `additionalProperties == False`. It enforces that the keys of an object - conform to any of the specified validators in the mapping, and its value - has the specified type. - - Examples:: - - counts = Nested( - SomeSchema, - validate=PatternProperties( - {Regexp('^0x([0-9A-Fa-f])+$'): Integer(), - Regexp('OTHER_THING'): String()}) - - """ - - def __init__(self, pattern_properties): - """PatternProperties initializer. - - Args: - pattern_properties (dict[Validator: Field]): dictionary of the - valid mappings. - """ - self.pattern_properties = pattern_properties - - def __call__(self, value): - errors = {} - - if isinstance(value, Mapping): - _dict = value - else: - _dict = value.__dict__ - - for key, value_ in _dict.items(): - # Attempt to validate the keys against any field. - field = None - for validator, candidate in self.pattern_properties.items(): - try: - validator(key) - field = candidate - except ValidationError as ex: - errors[key] = ex.messages - - # Attempt to validate the contents. - if field: - errors.pop(key, None) - try: - field.deserialize(value_) - except ValidationError as ex: - errors[key] = ex.messages - - if errors: - raise ValidationError(errors) - - return value diff --git a/requirements.txt b/requirements.txt index 87cb51627865..d1ba2d9da813 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ jsonschema>=2.6 -marshmallow>=3,<4 -marshmallow_polyfield>=5.7,<6 networkx>=2.2;python_version>'3.5' networkx>=2.2,<2.4;python_version=='3.5' retworkx>=0.3.2 diff --git a/setup.py b/setup.py index aa4dc98ded19..6269bf09a344 100755 --- a/setup.py +++ b/setup.py @@ -26,8 +26,6 @@ REQUIREMENTS = [ "jsonschema>=2.6", - "marshmallow>=3,<4", - "marshmallow_polyfield>=5.7,<6", "networkx>=2.2;python_version>'3.5'", # Networkx 2.4 is the final version with python 3.5 support. "networkx>=2.2,<2.4;python_version=='3.5'", diff --git a/test/python/validation/__init__.py b/test/python/validation/__init__.py deleted file mode 100644 index 6147b364e7e6..000000000000 --- a/test/python/validation/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test for the validation module.""" diff --git a/test/python/validation/test_custom_fields.py b/test/python/validation/test_custom_fields.py deleted file mode 100644 index 4e923e29f0ea..000000000000 --- a/test/python/validation/test_custom_fields.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test terra custom fields.""" - -import numpy -import sympy - -from qiskit.circuit import Parameter - -from qiskit.test import QiskitTestCase -from qiskit.validation import fields -from qiskit.validation.base import BaseModel, BaseSchema, bind_schema -from qiskit.validation.exceptions import ModelValidationError - - -class DogSchema(BaseSchema): - """Example Dog schema.""" - barking = fields.InstructionParameter(required=True) - - -@bind_schema(DogSchema) -class Dog(BaseModel): - """Example Dog model.""" - pass - - -class TestFields(QiskitTestCase): - """Tests for Fields.""" - - def test_parameter_field_float(self): - """Test InstructionParameter with types equivalent to float.""" - dog = Dog(barking=0.1) - dog_numpy = Dog(barking=numpy.dtype('float').type(0.1)) - - self.assertEqual(dog_numpy.barking, - numpy.dtype('float').type(0.1)) - self.assertEqual(dog.to_dict(), dog_numpy.to_dict()) - - def test_parameter_field_complex(self): - """Test InstructionParameter with types equivalent to complex.""" - dog = Dog(barking=(0.1+0.2j)) - dog_numpy = Dog(barking=numpy.dtype('complex').type(0.1+0.2j)) - - self.assertEqual(dog_numpy.barking, - numpy.dtype('complex').type(0.1+0.2j)) - self.assertEqual(dog.to_dict(), dog_numpy.to_dict()) - self.assertEqual(dog.to_dict()['barking'], [0.1, 0.2]) - - def test_parameter_field_int(self): - """Test InstructionParameter with types equivalent to int.""" - dog = Dog(barking=1) - dog_numpy = Dog(barking=numpy.dtype('int').type(1)) - - self.assertEqual(dog_numpy.barking, - numpy.dtype('int').type(1)) - self.assertEqual(dog.to_dict(), dog_numpy.to_dict()) - - def test_parameter_field_str(self): - """Test InstructionParameter with types equivalent to str.""" - dog = Dog(barking='woof') - - self.assertEqual(dog.barking, 'woof') - self.assertEqual(dog.to_dict(), {'barking': 'woof'}) - - def test_parameter_field_container(self): - """Test InstructionParameter with container types.""" - dog = Dog(barking=[0.1, 2.3]) - dog_numpy = Dog(barking=numpy.array([0.1, 2.3])) - dog_complex = Dog(barking=complex(0.1, 2.3)) - - self.assertEqual(dog.to_dict(), dog_numpy.to_dict()) - self.assertEqual(dog.to_dict(), dog_complex.to_dict()) - - def test_parameter_field_container_nested(self): - """Test InstructionParameter with nested container types.""" - dog = Dog(barking=[[1, 2]]) - dog_numpy = Dog(barking=numpy.array([[1, 2]])) - - self.assertEqual(dog.to_dict(), dog_numpy.to_dict()) - - def test_parameter_field_from_dict(self): - """Test InstructionParameter from_dict.""" - dog = Dog.from_dict({'barking': [0.1, 2]}) - self.assertEqual(dog.barking, [0.1, 2]) - - def test_parameter_field_invalid(self): - """Test InstructionParameter invalid values.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = Dog(barking={}) - self.assertIn('barking', str(context_manager.exception)) - - with self.assertRaises(ModelValidationError) as context_manager: - # Invalid types inside containers are also not allowed. - _ = Dog(barking=[{}]) - self.assertIn('barking', str(context_manager.exception)) - - def test_parameter_field_from_dict_invalid(self): - """Test InstructionParameter from_dict.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = Dog.from_dict({'barking': {}}) - self.assertIn('barking', str(context_manager.exception)) - - with self.assertRaises(ModelValidationError) as context_manager: - # Invalid types inside containers are also not allowed. - _ = Dog.from_dict({'barking': [{}]}) - self.assertIn('barking', str(context_manager.exception)) - - with self.assertRaises(ModelValidationError) as context_manager: - # Non-basic types cannot be used in from_dict, even if they are - # accepted in the constructor. This is by design, as the serialized - # form should only contain Python/json-schema basic types. - _ = Dog.from_dict({'barking': sympy.Symbol('woof')}) - self.assertIn('barking', str(context_manager.exception)) - - def test_parameter_expression_fully_bound(self): - """Test ParameterExpressions valid after fully bound.""" - x = Parameter('x') - y = Parameter('y') - - expr = x - bound_expr = expr.bind({x: 2}) - - dog_expr = Dog(barking=bound_expr) - dog_float = Dog(barking=2) - self.assertEqual(dog_expr.barking, bound_expr) - self.assertEqual(dog_expr.to_dict(), dog_float.to_dict()) - - expr = x + y - bound_expr = expr.bind({x: 2, y: 3}) - - dog_expr = Dog(barking=bound_expr) - dog_float = Dog(barking=5) - self.assertEqual(dog_expr.barking, bound_expr) - self.assertEqual(dog_expr.to_dict(), dog_float.to_dict()) - - def test_parameter_expression_partially_bound(self): - """Test ParameterExpressions invalid if partially bound.""" - x = Parameter('x') - y = Parameter('y') - - with self.assertRaises(ModelValidationError) as context_manager: - _ = Dog(barking=x).to_dict() - self.assertIn('barking', str(context_manager.exception)) - - expr = x + y - - with self.assertRaises(ModelValidationError) as context_manager: - _ = Dog(barking=expr).to_dict() - self.assertIn('barking', str(context_manager.exception)) - - partially_bound_expr = expr.bind({x: 2}) - - with self.assertRaises(ModelValidationError) as context_manager: - _ = Dog(barking=partially_bound_expr).to_dict() - self.assertIn('barking', str(context_manager.exception)) diff --git a/test/python/validation/test_fields.py b/test/python/validation/test_fields.py deleted file mode 100644 index 3a2a7dc9b24b..000000000000 --- a/test/python/validation/test_fields.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test polymorphic and validated fields.""" - -from qiskit.validation import fields -from qiskit.validation.base import BaseModel, BaseSchema, bind_schema -from qiskit.validation.exceptions import ModelValidationError -from qiskit.validation.fields import TryFrom, ByAttribute, ByType -from qiskit.test import QiskitTestCase - - -class DummySchema(BaseSchema): - """Simple Dummy schema.""" - pass - - -class DogSchema(BaseSchema): - """Example Dog schema.""" - barking_power = fields.Integer(required=True) - - -class CatSchema(BaseSchema): - """Example Cat schema.""" - fur_density = fields.Float(required=True) - - -class PetOwnerSchema(BaseSchema): - """Example PetOwner schema, with different polymorphic fields.""" - auto_pets = TryFrom([CatSchema, DogSchema], many=True) - by_attribute_pets = ByAttribute({'fur_density': CatSchema, - 'barking_power': DogSchema}, many=True) - by_type_contact = ByType([fields.Email(), fields.Url()]) - - -@bind_schema(DummySchema) -class NotAPet(BaseModel): - """Simple NotAPet model.""" - pass - - -@bind_schema(DogSchema) -class Dog(BaseModel): - """Example Dog model.""" - pass - - -@bind_schema(CatSchema) -class Cat(BaseModel): - """Example Cat model.""" - pass - - -@bind_schema(PetOwnerSchema) -class PetOwner(BaseModel): - """Example PetOwner model.""" - pass - - -class TestFields(QiskitTestCase): - """Tests for Fields.""" - - def test_try_from_field_instantiate(self): - """Test the TryFrom field, instantiation.""" - pet_owner = PetOwner(auto_pets=[Cat(fur_density=1.5), - Dog(barking_power=100)]) - self.assertIsInstance(pet_owner.auto_pets[0], Cat) - self.assertIsInstance(pet_owner.auto_pets[1], Dog) - self.assertEqual(pet_owner.auto_pets[0].fur_density, 1.5) - - def test_try_from_field_instantiate_from_dict(self): - """Test the TryFrom field, instantiation from dict.""" - pet_owner = PetOwner.from_dict({'auto_pets': [{'fur_density': 1.5}, - {'barking_power': 100}]}) - self.assertIsInstance(pet_owner.auto_pets[0], Cat) - self.assertIsInstance(pet_owner.auto_pets[1], Dog) - self.assertEqual(pet_owner.auto_pets[0].fur_density, 1.5) - - def test_try_from_field_invalid(self): - """Test the TryFrom field, with invalid kind of object.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = PetOwner(auto_pets=[Cat(fur_density=1.5), NotAPet()]) - self.assertIn('auto_pets', str(context_manager.exception)) - - def test_try_from_field_invalid_from_dict(self): - """Test the TryFrom field, instantiation from dict, with invalid.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = PetOwner.from_dict({'auto_pets': [{'fur_density': 1.5}, - {'name': 'John Doe'}]}) - self.assertIn('auto_pets', str(context_manager.exception)) - - def test_by_attribute_field_instantiate(self): - """Test the ByAttribute field, instantiation.""" - pet_owner = PetOwner(by_attribute_pets=[Cat(fur_density=1.5), - Dog(barking_power=100)]) - self.assertIsInstance(pet_owner.by_attribute_pets[0], Cat) - self.assertIsInstance(pet_owner.by_attribute_pets[1], Dog) - self.assertEqual(pet_owner.by_attribute_pets[0].fur_density, 1.5) - - def test_by_attribute_field_instantiate_from_dict(self): - """Test the ByAttribute field, instantiation from dict.""" - pet_owner = PetOwner.from_dict({'by_attribute_pets': [{'fur_density': 1.5}, - {'barking_power': 100}]}) - self.assertIsInstance(pet_owner.by_attribute_pets[0], Cat) - self.assertIsInstance(pet_owner.by_attribute_pets[1], Dog) - self.assertEqual(pet_owner.by_attribute_pets[0].fur_density, 1.5) - - def test_by_attribute_field_invalid(self): - """Test the ByAttribute field, with invalid kind of object.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = PetOwner(by_attribute_pets=[Cat(fur_density=1.5), NotAPet()]) - self.assertIn('by_attribute_pets', str(context_manager.exception)) - - def test_by_attribute_field_invalid_from_dict(self): - """Test the ByAttribute field, instantiation from dict, with invalid.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = PetOwner.from_dict({'by_attribute_pets': [{'fur_density': 1.5}, - {'name': 'John Doe'}]}) - self.assertIn('by_attribute_pets', str(context_manager.exception)) - - def test_by_type_field_instantiate(self): - """Test the ByType field, instantiation.""" - pet_owner = PetOwner(by_type_contact='foo@bar.com') - self.assertEqual(pet_owner.by_type_contact, 'foo@bar.com') - - def test_by_type_field_instantiate_from_dict(self): - """Test the ByType field, instantiation from dict.""" - pet_owner = PetOwner.from_dict({'by_type_contact': 'foo@bar.com'}) - self.assertEqual(pet_owner.by_type_contact, 'foo@bar.com') - - def test_by_type_field_invalid(self): - """Test the ByType field, with invalid kind of object.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = PetOwner(by_type_contact=123) - self.assertIn('by_type_contact', str(context_manager.exception)) - - def test_by_type_field_invalid_from_dict(self): - """Test the ByType field, instantiation from dict, with invalid.""" - with self.assertRaises(ModelValidationError) as context_manager: - _ = PetOwner.from_dict({'by_type_contact': 123}) - self.assertIn('by_type_contact', str(context_manager.exception)) diff --git a/test/python/validation/test_models.py b/test/python/validation/test_models.py deleted file mode 100644 index 6d6b50170844..000000000000 --- a/test/python/validation/test_models.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Models tests.""" - -from datetime import datetime - -from qiskit.validation import fields -from qiskit.validation.base import BaseModel, BaseSchema, bind_schema -from qiskit.validation.exceptions import ModelValidationError -from qiskit.test import QiskitTestCase - - -class DummySchema(BaseSchema): - """Example Dummy schema.""" - pass - - -class PersonSchema(BaseSchema): - """Example Person schema.""" - name = fields.String(required=True) - birth_date = fields.Date() - email = fields.Email() - - -class BookSchema(BaseSchema): - """Example Book schema.""" - title = fields.String(required=True) - date = fields.Date() - author = fields.Nested(PersonSchema, required=True) - - -@bind_schema(DummySchema) -class NotAPerson(BaseModel): - """Example of NotAPerson model.""" - pass - - -@bind_schema(PersonSchema) -class Person(BaseModel): - """Example Person model.""" - pass - - -@bind_schema(BookSchema) -class Book(BaseModel): - """Example Book model.""" - pass - - -class TestModels(QiskitTestCase): - """Tests for models.""" - - def test_instantiate(self): - """Test model instantiation.""" - person = Person(name='Foo') - self.assertEqual(person.name, 'Foo') - - # From dict. - person_dict = Person.from_dict({'name': 'Foo'}) - self.assertEqual(person_dict.name, 'Foo') - - self.assertEqual(person, person_dict) - - def test_instantiate_required(self): - """Test model instantiation without required fields.""" - with self.assertRaises(ModelValidationError): - _ = Person() - - # From dict. - with self.assertRaises(ModelValidationError): - _ = Person.from_dict({}) - - def test_instantiate_wrong_type(self): - """Test model instantiation with fields of the wrong type.""" - with self.assertRaises(ModelValidationError): - _ = Person(name=1) - - # From dict. - with self.assertRaises(ModelValidationError): - _ = Person.from_dict({'name': 1}) - - def test_instantiate_deserialized_types(self): - """Test model instantiation with fields of deserialized type.""" - birth_date = datetime(2000, 1, 1).date() - - person = Person(name='Foo', birth_date=birth_date) - self.assertEqual(person.birth_date, birth_date) - with self.assertRaises(ModelValidationError): - _ = Person(name='Foo', birth_date=birth_date.isoformat()) - - # From dict. - person_dict = Person.from_dict({'name': 'Foo', - 'birth_date': birth_date.isoformat()}) - self.assertEqual(person_dict.birth_date, birth_date) - with self.assertRaises(ModelValidationError): - _ = Person.from_dict({'name': 'Foo', 'birth_date': birth_date}) - - self.assertEqual(person, person_dict) - - def test_instantiate_additional(self): - """Test model instantiation with additional fields.""" - person = Person(name='Foo', other='bar') - self.assertEqual(person.name, 'Foo') - self.assertEqual(person.other, 'bar') - - # From dict. - person_dict = Person.from_dict({'name': 'Foo', 'other': 'bar'}) - self.assertEqual(person_dict.name, 'Foo') - self.assertEqual(person_dict.other, 'bar') - - self.assertEqual(person, person_dict) - - def test_instantiate_nested(self): - """Test model instantiation with nested fields.""" - book = Book(title='A Book', author=Person(name='Foo', other='bar')) - self.assertEqual(book.title, 'A Book') - self.assertEqual(book.author.name, 'Foo') - self.assertEqual(book.author.other, 'bar') - with self.assertRaises(AttributeError): - _ = book.date - - # From dict. - book_dict = Book.from_dict({'title': 'A Book', - 'author': {'name': 'Foo', 'other': 'bar'}}) - self.assertEqual(book_dict.title, 'A Book') - self.assertEqual(book_dict.author.name, 'Foo') - self.assertEqual(book_dict.author.other, 'bar') - with self.assertRaises(AttributeError): - _ = book_dict.date - - self.assertEqual(book, book_dict) - - def test_instantiate_nested_wrong_type(self): - """Test model instantiation with nested fields of the wrong type.""" - with self.assertRaises(ModelValidationError): - _ = Book(title='A Book', author=NotAPerson()) - - # From dict. - with self.assertRaises(ModelValidationError): - _ = Book.from_dict({'title': 'A Book', - 'author': {'fur_density': '1.2'}}) - - def test_serialize(self): - """Test model serialization to dict.""" - person = Person(name='Foo', other='bar') - self.assertEqual(person.to_dict(), - {'name': 'Foo', 'other': 'bar'}) - - def test_serialize_nested(self): - """Test model serialization to dict, with nested fields.""" - book = Book(title='A Book', author=Person(name='Foo', other='bar')) - self.assertEqual(book.to_dict(), - {'title': 'A Book', - 'author': {'name': 'Foo', 'other': 'bar'}}) - - def test_instantiate_no_validation(self): - """Test model instantiation without validation.""" - person = Person(name='Foo', birth_date='INVALID', validate=False) - self.assertEqual(person.name, 'Foo') - self.assertEqual(person.birth_date, 'INVALID') diff --git a/test/python/validation/test_schemas.py b/test/python/validation/test_schemas.py deleted file mode 100644 index da689339283a..000000000000 --- a/test/python/validation/test_schemas.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test qiskit schema binding.""" - -from qiskit.validation.base import BaseModel, BaseSchema, bind_schema -from qiskit.test import QiskitTestCase - - -class TestSchemas(QiskitTestCase): - """Tests for operations with schemas.""" - - def test_double_binding(self): - """Trying to bind a schema twice must raise.""" - class _DummySchema(BaseSchema): - pass - - @bind_schema(_DummySchema) - class _DummyModel(BaseModel): - pass - - with self.assertRaises(ValueError): - @bind_schema(_DummySchema) - class _AnotherModel(BaseModel): - pass - - def test_schema_reusing(self): - """Reusing a schema is possible if subclassing.""" - class _DummySchema(BaseSchema): - pass - - class _SchemaCopy(_DummySchema): - pass - - @bind_schema(_DummySchema) - class _DummyModel(BaseModel): - pass - - try: - @bind_schema(_SchemaCopy) - class _AnotherModel(BaseModel): - pass - except ValueError: - self.fail('`bind_schema` raised while binding.') - - def test_binding_params(self): - """Test using parameters in schema binding.""" - class _DummySchema(BaseSchema): - pass - - # Set `many=True` as an example of a marshmallow parameter. - @bind_schema(_DummySchema, many=True) - class _DummyModel(BaseModel): - pass - - # By using `many=True`, the serialization outputs a list. - dummy_models = _DummyModel.from_dict([{}]) - self.assertIsInstance(dummy_models, list) - self.assertTrue(all([isinstance(obj, _DummyModel) for obj in dummy_models])) diff --git a/test/python/validation/test_validators.py b/test/python/validation/test_validators.py deleted file mode 100644 index 9444d2021671..000000000000 --- a/test/python/validation/test_validators.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test qiskit validators.""" - -from marshmallow.validate import Regexp - -from qiskit.validation import fields, ModelValidationError -from qiskit.validation.base import BaseModel, BaseSchema, bind_schema, ObjSchema, Obj -from qiskit.validation.validate import PatternProperties -from qiskit.test import QiskitTestCase - - -class HistogramSchema(BaseSchema): - """Example HistogramSchema schema with strict dict structure validation.""" - counts = fields.Nested(ObjSchema, validate=PatternProperties({ - Regexp('^0x([0-9A-Fa-f])+$'): fields.Integer() - })) - - -@bind_schema(HistogramSchema) -class Histogram(BaseModel): - """Example Histogram model.""" - pass - - -class TestValidators(QiskitTestCase): - """Test for validators.""" - - def test_patternproperties_valid(self): - """Test the PatternProperties validator allowing fine control on keys and values.""" - counts_dict = {'0x00': 50, '0x11': 50} - counts = Obj(**counts_dict) - histogram = Histogram(counts=counts) - self.assertEqual(histogram.counts, counts) - - # From dict - histogram = Histogram.from_dict({'counts': counts_dict}) - self.assertEqual(histogram.counts, counts) - - def test_patternproperties_invalid_key(self): - """Test the PatternProperties validator fails when invalid key""" - invalid_key_data = {'counts': {'00': 50, '0x11': 50}} - with self.assertRaises(ModelValidationError): - _ = Histogram(**invalid_key_data) - - # From dict - with self.assertRaises(ModelValidationError): - _ = Histogram.from_dict(invalid_key_data) - - def test_patternproperties_invalid_value(self): - """Test the PatternProperties validator fails when invalid value""" - invalid_value_data = {'counts': {'0x00': 'so many', '0x11': 50}} - with self.assertRaises(ModelValidationError): - _ = Histogram(**invalid_value_data) - - # From dict - with self.assertRaises(ModelValidationError): - _ = Histogram.from_dict(invalid_value_data) - - def test_patternproperties_to_dict(self): - """Test a field using the PatternProperties validator produces a correct value""" - counts_dict = {'0x00': 50, '0x11': 50} - counts = Obj(**counts_dict) - histogram = Histogram(counts=counts) - histogram_dict = histogram.to_dict() - self.assertEqual(histogram_dict, {'counts': counts_dict}) From b808d203948580c68d2b973a5dc07e12a4261939 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 10 Apr 2020 11:24:39 -0400 Subject: [PATCH 2/8] Fixes from testing Now #4016 has merged this is unblocked on the terra side. This commit makes some changes and fixes issues found from testing. --- qiskit/result/models.py | 36 ++++++++++++++++++++++++++--- qiskit/result/postprocess.py | 4 ++++ qiskit/result/result.py | 38 +++++++++++++++++++++++++------ qiskit/result/utils.py | 4 ++-- test/python/result/test_result.py | 33 +++++++++++++-------------- 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/qiskit/result/models.py b/qiskit/result/models.py index 1a1759401cb9..f57903b0bd41 100644 --- a/qiskit/result/models.py +++ b/qiskit/result/models.py @@ -18,6 +18,7 @@ from types import SimpleNamespace from qiskit.qobj.utils import MeasReturnType, MeasLevel +from qiskit.qobj import QobjExperimentHeader from qiskit.validation.exceptions import ModelValidationError @@ -91,7 +92,8 @@ def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, status (str): The status of the experiment seed (int): The seed used for simulation (if run on a simulator) meas_return (str): The type of measurement returned - header (dict): A free form dictionary header for the experiment + header (qiskit.qobj.QobjExperimentHeader): A free form dictionary + header for the experiment kwargs: Arbitrary extra fields Raises: @@ -101,6 +103,8 @@ def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, self.success = success self.data = data self.meas_level = meas_level + if header is not None: + self.header = header if status is not None: self.status = status if seed is not None: @@ -121,12 +125,19 @@ def to_dict(self): for field in self.__dict__.keys(): if field not in ['shots', 'success', 'data', 'meas_level']: out_dict[field] = getattr(self, field) + elif field == 'header': + out_dict['header'] = self.header.to_dict() + print(out_dict) return out_dict @classmethod def from_dict(cls, data): - in_data = copy.copy() + in_data = copy.copy(data) in_data['data'] = ExperimentResultData.from_dict(in_data.pop('data')) + if 'header' in in_data: +# print(in_data['header']) + in_data['header'] = QobjExperimentHeader.from_dict( + in_data.pop('header')) return cls(**in_data) def __getstate__(self): @@ -136,4 +147,23 @@ def __setstate__(self, state): return self.from_dict(state) def __reduce__(self): - return (self.__class__, (self.shots, self.success, self.data)) + args = [self.shots, self.success, self.data, self.meas_level] + if hasattr(self, 'status'): + args.append(self.status) + else: + args.append(None) + if hasattr(self, 'seed'): + args.append(self.seed) + else: + args.append(None) + if hasattr(self, 'meas_return'): + args.append(self.meas_return) + else: + args.append(None) + if hasattr(self, 'header'): + args.append(self.header) + else: + args.append(None) + + state = self.to_dict() + return (self.__class__, tuple(args), state) diff --git a/qiskit/result/postprocess.py b/qiskit/result/postprocess.py index 760b048f76de..c4f7289b9a11 100644 --- a/qiskit/result/postprocess.py +++ b/qiskit/result/postprocess.py @@ -184,6 +184,8 @@ def format_statevector(vec, decimals=None): Returns: list[complex]: a list of python complex numbers. """ + if isinstance(vec, np.ndarray): + return vec num_basis = len(vec) vec_complex = np.zeros(num_basis, dtype=complex) for i in range(num_basis): @@ -204,6 +206,8 @@ def format_unitary(mat, decimals=None): Returns: list[list[complex]]: a matrix of complex numbers """ + if isinstance(mat, np.ndarray): + return mat num_basis = len(mat) mat_complex = np.zeros((num_basis, num_basis), dtype=complex) for i, vec in enumerate(mat): diff --git a/qiskit/result/result.py b/qiskit/result/result.py index e2d2f6749f91..e3b3f3935056 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -24,6 +24,7 @@ from qiskit.result.models import ExperimentResult from qiskit.result import postprocess from qiskit.qobj.utils import MeasLevel +from qiskit.qobj import QobjHeader class Result(SimpleNamespace): @@ -44,14 +45,19 @@ class Result(SimpleNamespace): """ def __init__(self, backend_name, backend_version, qobj_id, job_id, success, - results, date=None, status=None, header=None, date=None, - status=None, header=None, **kwargs): + results, date=None, status=None, header=None, **kwargs): self.backend_name = backend_name self.backend_version = backend_version self.qobj_id = qobj_id self.job_id = job_id self.success = success self.results = results + if date is not None: + self.date = date + if status is not None: + self.status = status + if header is not None: + self.header = header self.__dict__.update(kwargs) def to_dict(self): @@ -67,14 +73,18 @@ def to_dict(self): if field not in ['backend_name', 'backend_version', 'qobj_id', 'job_id', 'success', 'results']: out_dict[field] = getattr(self, field) + elif field == 'header': + out_dict[field] = self.header.to_dict() return out_dict @classmethod def from_dict(cls, data): - in_data = copy.copy() + in_data = copy.copy(data) in_data['results'] = [ ExperimentResult.from_dict(x) for x in in_data.pop('results')] - cls(**in_data) + if 'header' in in_data: + in_data['header'] = QobjHeader.from_dict(in_data.pop('header')) + return cls(**in_data) def __getstate__(self): return self.to_dict() @@ -83,9 +93,23 @@ def __setstate__(self, state): return self.from_dict(state) def __reduce__(self): - return (self.__class__, (self.backend_name, self.backend_version, - self.qobj_id, self.job_id, self.success, - self.results)) + args = [self.backend_name, self.backend_version, + self.qobj_id, self.job_id, self.success, + self.results] + if hasattr(self, 'date'): + args.append(self.date) + else: + args.append(None) + if hasattr(self, 'status'): + args.append(self.status) + else: + args.append(None) + if hasattr(self, 'header'): + args.append(self.header) + else: + args.append(None) + state = self.to_dict() + return (self.__class__, tuple(args), state) def data(self, experiment=None): """Get the raw data for an experiment. diff --git a/qiskit/result/utils.py b/qiskit/result/utils.py index 8da06eb29942..bed620ea5d46 100644 --- a/qiskit/result/utils.py +++ b/qiskit/result/utils.py @@ -16,7 +16,7 @@ from functools import reduce from re import match -from qiskit.validation.base import Obj + from qiskit.exceptions import QiskitError @@ -46,7 +46,7 @@ def marginal_counts(result, indices=None): new_counts_hex = {} for k, v in new_counts.items(): new_counts_hex[_bin_to_hex(k)] = v - experiment_result.data.counts = Obj(**new_counts_hex) + experiment_result.data.counts = new_counts_hex experiment_result.header.memory_slots = len(indices) else: counts = result diff --git a/test/python/result/test_result.py b/test/python/result/test_result.py index fbcf1cde4829..5e99f81af770 100644 --- a/test/python/result/test_result.py +++ b/test/python/result/test_result.py @@ -18,7 +18,6 @@ from qiskit.result import models from qiskit.result import marginal_counts -from qiskit.validation import base from qiskit.result import Result from qiskit.test import QiskitTestCase @@ -40,7 +39,7 @@ def test_counts_no_header(self): raw_counts = {'0x0': 4, '0x2': 10} no_header_processed_counts = {bin(int(bs[2:], 16))[2:]: counts for (bs, counts) in raw_counts.items()} - data = models.ExperimentResultData(counts=base.Obj(**raw_counts)) + data = models.ExperimentResultData(counts=dict(**raw_counts)) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data) result = Result(results=[exp_result], **self.base_result_args) @@ -50,8 +49,8 @@ def test_counts_header(self): """Test that counts are extracted properly with header.""" raw_counts = {'0x0': 4, '0x2': 10} processed_counts = {'0 0 00': 4, '0 0 10': 10} - data = models.ExperimentResultData(counts=base.Obj(**raw_counts)) - exp_result_header = base.Obj(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], + data = models.ExperimentResultData(counts=dict(**raw_counts)) + exp_result_header = dict(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], memory_slots=4) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data, header=exp_result_header) @@ -70,22 +69,22 @@ def test_multiple_circuits_counts(self): """ raw_counts_1 = {'0x0': 5, '0x3': 12, '0x5': 9, '0xD': 6, '0xE': 2} processed_counts_1 = {'0000': 5, '0011': 12, '0101': 9, '1101': 6, '1110': 2} - data_1 = models.ExperimentResultData(counts=base.Obj(**raw_counts_1)) - exp_result_header_1 = base.Obj(creg_sizes=[['c0', 4]], memory_slots=4) + data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1)) + exp_result_header_1 = dict(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_1 = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data_1, header=exp_result_header_1) raw_counts_2 = {'0x1': 0, '0x4': 3, '0x6': 6, '0xA': 1, '0xB': 2} processed_counts_2 = {'0001': 0, '0100': 3, '0110': 6, '1010': 1, '1011': 2} - data_2 = models.ExperimentResultData(counts=base.Obj(**raw_counts_2)) - exp_result_header_2 = base.Obj(creg_sizes=[['c0', 4]], memory_slots=4) + data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2)) + exp_result_header_2 = dict(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_2 = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data_2, header=exp_result_header_2) raw_counts_3 = {'0xC': 27, '0xF': 20} processed_counts_3 = {'1100': 27, '1111': 20} - data_3 = models.ExperimentResultData(counts=base.Obj(**raw_counts_3)) - exp_result_header_3 = base.Obj(creg_sizes=[['c0', 4]], memory_slots=4) + data_3 = models.ExperimentResultData(counts=dict(**raw_counts_3)) + exp_result_header_3 = dict(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_3 = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data_3, header=exp_result_header_3) @@ -100,8 +99,8 @@ def test_multiple_circuits_counts(self): def test_marginal_counts(self): """Test that counts are marginalized correctly.""" raw_counts = {'0x0': 4, '0x1': 7, '0x2': 10, '0x6': 5, '0x9': 11, '0xD': 9, '0xE': 8} - data = models.ExperimentResultData(counts=base.Obj(**raw_counts)) - exp_result_header = base.Obj(creg_sizes=[['c0', 4]], memory_slots=4) + data = models.ExperimentResultData(counts=dict(**raw_counts)) + exp_result_header = dict(creg_sizes=[['c0', 4]], memory_slots=4) exp_result = models.ExperimentResult(shots=54, success=True, data=data, header=exp_result_header) result = Result(results=[exp_result], **self.base_result_args) @@ -113,14 +112,14 @@ def test_marginal_counts(self): def test_marginal_counts_result(self): """Test that a Result object containing counts marginalizes correctly.""" raw_counts_1 = {'0x0': 4, '0x1': 7, '0x2': 10, '0x6': 5, '0x9': 11, '0xD': 9, '0xE': 8} - data_1 = models.ExperimentResultData(counts=base.Obj(**raw_counts_1)) - exp_result_header_1 = base.Obj(creg_sizes=[['c0', 4]], memory_slots=4) + data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1)) + exp_result_header_1 = dict(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_1 = models.ExperimentResult(shots=54, success=True, data=data_1, header=exp_result_header_1) raw_counts_2 = {'0x2': 5, '0x3': 8} - data_2 = models.ExperimentResultData(counts=base.Obj(**raw_counts_2)) - exp_result_header_2 = base.Obj(creg_sizes=[['c0', 2]], memory_slots=2) + data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2)) + exp_result_header_2 = dict(creg_sizes=[['c0', 2]], memory_slots=2) exp_result_2 = models.ExperimentResult(shots=13, success=True, data=data_2, header=exp_result_header_2) @@ -151,7 +150,7 @@ def test_memory_counts_header(self): no_header_processed_memory = ['0 0 00', '0 0 00', '0 0 10', '0 0 10', '0 0 10', '0 0 10', '0 0 10'] data = models.ExperimentResultData(memory=raw_memory) - exp_result_header = base.Obj(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], + exp_result_header = dict(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], memory_slots=4) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, memory=True, data=data, From 1f5deadcdbfed2fb35a2f6edf909abed6c86cd68 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Apr 2020 11:32:15 -0400 Subject: [PATCH 3/8] Abandon SimpleNamespace for Result and ExperimentResult While both Result and ExperimentResult take arbitrary key value date via the kwargs using SimpleNamespace as a parent class like in other places is not a good fit. This is because the signatures for these classes are signfiicantly different from the base SimpleNamespace. So when Results are pickled via async execution (like in the BasicAer and Aer providers) this causes it to either fail or if a custom __reduce__ method is defined it to not work as expected. This commit pivots the new classes to be bare objects that store the arbitrary kwargs as a private dictionary attribute. Attribute access for these fields are implemented via a custom __getattr__. --- qiskit/result/models.py | 68 +++++++++++++------------------ qiskit/result/result.py | 51 ++++++++--------------- test/python/result/test_result.py | 4 +- 3 files changed, 48 insertions(+), 75 deletions(-) diff --git a/qiskit/result/models.py b/qiskit/result/models.py index f57903b0bd41..f9d8dc00dad3 100644 --- a/qiskit/result/models.py +++ b/qiskit/result/models.py @@ -36,7 +36,8 @@ def __init__(self, counts=None, snapshots=None, memory=None, snapshots (dict): A dictionary where the key is the snapshot slot and the value is a dictionary of the snapshots for that slot. - memory (list) + memory (list): A list of results per shot if the run had + memory enabled statevector (list or numpy.array): A list or numpy array of the statevector result unitary (list or numpy.array): A list or numpy arrray of the @@ -64,10 +65,11 @@ def to_dict(self): @classmethod def from_dict(cls, data): - return cls(**data) + in_data = copy.copy(data) + return cls(**in_data) -class ExperimentResult(SimpleNamespace): +class ExperimentResult: """Class representing an Experiment Result. Attributes: @@ -77,6 +79,8 @@ class ExperimentResult(SimpleNamespace): meas_level (int): Measurement result level. """ + _metadata = {} + def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, status=None, seed=None, meas_return=None, header=None, **kwargs): @@ -99,6 +103,7 @@ def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, Raises: QiskitError: If meas_return or meas_level are not valid values """ + self._metadata = {} self.shots = shots self.success = success self.data = data @@ -113,7 +118,13 @@ def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, if meas_return not in list(MeasReturnType): raise ModelValidationError('%s not a valid meas_return value') self.meas_return = meas_return - self.__dict__.update(kwargs) + self._metadata.update(kwargs) + + def __getattr__(self, name): + try: + return self._metadata[name] + except KeyError: + raise AttributeError('Attribute %s is not defined' % name) def to_dict(self): out_dict = { @@ -122,48 +133,25 @@ def to_dict(self): 'data': self.data.to_dict(), 'meas_level': self.meas_level, } - for field in self.__dict__.keys(): - if field not in ['shots', 'success', 'data', 'meas_level']: - out_dict[field] = getattr(self, field) - elif field == 'header': - out_dict['header'] = self.header.to_dict() - print(out_dict) + if hasattr(self, 'header'): + out_dict['header'] = self.header.to_dict() + if hasattr(self, 'status'): + out_dict['status'] = self.status + if hasattr(self, 'seed'): + out_dict['seed'] = self.seed + if hasattr(self, 'meas_return'): + out_dict['meas_return'] = self.meas_return + out_dict.update(self._metadata) return out_dict @classmethod def from_dict(cls, data): in_data = copy.copy(data) - in_data['data'] = ExperimentResultData.from_dict(in_data.pop('data')) + data_obj = ExperimentResultData.from_dict(in_data.pop('data')) if 'header' in in_data: -# print(in_data['header']) in_data['header'] = QobjExperimentHeader.from_dict( in_data.pop('header')) - return cls(**in_data) - - def __getstate__(self): - return self.to_dict() - - def __setstate__(self, state): - return self.from_dict(state) - - def __reduce__(self): - args = [self.shots, self.success, self.data, self.meas_level] - if hasattr(self, 'status'): - args.append(self.status) - else: - args.append(None) - if hasattr(self, 'seed'): - args.append(self.seed) - else: - args.append(None) - if hasattr(self, 'meas_return'): - args.append(self.meas_return) - else: - args.append(None) - if hasattr(self, 'header'): - args.append(self.header) - else: - args.append(None) + shots = in_data.pop('shots') + success = in_data.pop('success') - state = self.to_dict() - return (self.__class__, tuple(args), state) + return cls(shots, success, data_obj, **in_data) diff --git a/qiskit/result/result.py b/qiskit/result/result.py index e3b3f3935056..a55c366fc21f 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -27,7 +27,7 @@ from qiskit.qobj import QobjHeader -class Result(SimpleNamespace): +class Result: """Model for Results. Please note that this class only describes the required fields. For the @@ -44,8 +44,11 @@ class Result(SimpleNamespace): experiments of the input qobj """ + _metadata = {} + def __init__(self, backend_name, backend_version, qobj_id, job_id, success, results, date=None, status=None, header=None, **kwargs): + self._metadata = {} self.backend_name = backend_name self.backend_version = backend_version self.qobj_id = qobj_id @@ -58,7 +61,7 @@ def __init__(self, backend_name, backend_version, qobj_id, job_id, success, self.status = status if header is not None: self.header = header - self.__dict__.update(kwargs) + self._metadata.update(kwargs) def to_dict(self): out_dict = { @@ -69,14 +72,21 @@ def to_dict(self): 'success': self.success, 'results': [x.to_dict() for x in self.results] } - for field in self.__dict__.keys(): - if field not in ['backend_name', 'backend_version', 'qobj_id', - 'job_id', 'success', 'results']: - out_dict[field] = getattr(self, field) - elif field == 'header': - out_dict[field] = self.header.to_dict() + if hasattr(self, 'date'): + out_dict['date'] = self.date + if hasattr(self, 'status'): + out_dict['status'] = self.status + if hasattr(self, 'header'): + out_dict[field] = self.header.to_dict() + out_dict.update(self._metadata) return out_dict + def __getattr__(self, name): + try: + return self._metadata[name] + except KeyError: + raise AttributeError('Attribute %s is not defined' % name) + @classmethod def from_dict(cls, data): in_data = copy.copy(data) @@ -86,31 +96,6 @@ def from_dict(cls, data): in_data['header'] = QobjHeader.from_dict(in_data.pop('header')) return cls(**in_data) - def __getstate__(self): - return self.to_dict() - - def __setstate__(self, state): - return self.from_dict(state) - - def __reduce__(self): - args = [self.backend_name, self.backend_version, - self.qobj_id, self.job_id, self.success, - self.results] - if hasattr(self, 'date'): - args.append(self.date) - else: - args.append(None) - if hasattr(self, 'status'): - args.append(self.status) - else: - args.append(None) - if hasattr(self, 'header'): - args.append(self.header) - else: - args.append(None) - state = self.to_dict() - return (self.__class__, tuple(args), state) - def data(self, experiment=None): """Get the raw data for an experiment. diff --git a/test/python/result/test_result.py b/test/python/result/test_result.py index 5e99f81af770..2050b8647bbb 100644 --- a/test/python/result/test_result.py +++ b/test/python/result/test_result.py @@ -51,7 +51,7 @@ def test_counts_header(self): processed_counts = {'0 0 00': 4, '0 0 10': 10} data = models.ExperimentResultData(counts=dict(**raw_counts)) exp_result_header = dict(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], - memory_slots=4) + memory_slots=4) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data, header=exp_result_header) result = Result(results=[exp_result], **self.base_result_args) @@ -151,7 +151,7 @@ def test_memory_counts_header(self): '0 0 10', '0 0 10', '0 0 10'] data = models.ExperimentResultData(memory=raw_memory) exp_result_header = dict(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], - memory_slots=4) + memory_slots=4) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, memory=True, data=data, header=exp_result_header) From d5d08432958b860cee34f29004fd38757f741865 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Apr 2020 11:41:13 -0400 Subject: [PATCH 4/8] Use correct header type in tests The results tests were using a raw Marshmallow.Obj object in the tests to construct the Result qobj headers and relying on the schema to convert it to a QobjExperimentHeader when it was passed to the Result constructor. When marshmallow was removed this was updated to a dict() since that was the closest mapping, but that ignored the transform marshmallow would do. This commit corrects that oversight and constructs QobjExperimentHeader object where dict was incorrectly used before. This is probably what the original tests should have done for clarity anyway. --- test/python/result/test_result.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/test/python/result/test_result.py b/test/python/result/test_result.py index 2050b8647bbb..d385bc37f623 100644 --- a/test/python/result/test_result.py +++ b/test/python/result/test_result.py @@ -19,6 +19,7 @@ from qiskit.result import models from qiskit.result import marginal_counts from qiskit.result import Result +from qiskit.qobj import QobjExperimentHeader from qiskit.test import QiskitTestCase @@ -50,8 +51,8 @@ def test_counts_header(self): raw_counts = {'0x0': 4, '0x2': 10} processed_counts = {'0 0 00': 4, '0 0 10': 10} data = models.ExperimentResultData(counts=dict(**raw_counts)) - exp_result_header = dict(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], - memory_slots=4) + exp_result_header = QobjExperimentHeader( + creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], memory_slots=4) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data, header=exp_result_header) result = Result(results=[exp_result], **self.base_result_args) @@ -70,21 +71,21 @@ def test_multiple_circuits_counts(self): raw_counts_1 = {'0x0': 5, '0x3': 12, '0x5': 9, '0xD': 6, '0xE': 2} processed_counts_1 = {'0000': 5, '0011': 12, '0101': 9, '1101': 6, '1110': 2} data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1)) - exp_result_header_1 = dict(creg_sizes=[['c0', 4]], memory_slots=4) + exp_result_header_1 = QobjExperimentHeader(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_1 = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data_1, header=exp_result_header_1) raw_counts_2 = {'0x1': 0, '0x4': 3, '0x6': 6, '0xA': 1, '0xB': 2} processed_counts_2 = {'0001': 0, '0100': 3, '0110': 6, '1010': 1, '1011': 2} data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2)) - exp_result_header_2 = dict(creg_sizes=[['c0', 4]], memory_slots=4) + exp_result_header_2 = QobjExperimentHeader(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_2 = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data_2, header=exp_result_header_2) raw_counts_3 = {'0xC': 27, '0xF': 20} processed_counts_3 = {'1100': 27, '1111': 20} data_3 = models.ExperimentResultData(counts=dict(**raw_counts_3)) - exp_result_header_3 = dict(creg_sizes=[['c0', 4]], memory_slots=4) + exp_result_header_3 = QobjExperimentHeader(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_3 = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data_3, header=exp_result_header_3) @@ -100,7 +101,8 @@ def test_marginal_counts(self): """Test that counts are marginalized correctly.""" raw_counts = {'0x0': 4, '0x1': 7, '0x2': 10, '0x6': 5, '0x9': 11, '0xD': 9, '0xE': 8} data = models.ExperimentResultData(counts=dict(**raw_counts)) - exp_result_header = dict(creg_sizes=[['c0', 4]], memory_slots=4) + exp_result_header = QobjExperimentHeader(creg_sizes=[['c0', 4]], + memory_slots=4) exp_result = models.ExperimentResult(shots=54, success=True, data=data, header=exp_result_header) result = Result(results=[exp_result], **self.base_result_args) @@ -113,13 +115,13 @@ def test_marginal_counts_result(self): """Test that a Result object containing counts marginalizes correctly.""" raw_counts_1 = {'0x0': 4, '0x1': 7, '0x2': 10, '0x6': 5, '0x9': 11, '0xD': 9, '0xE': 8} data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1)) - exp_result_header_1 = dict(creg_sizes=[['c0', 4]], memory_slots=4) + exp_result_header_1 = QobjExperimentHeader(creg_sizes=[['c0', 4]], memory_slots=4) exp_result_1 = models.ExperimentResult(shots=54, success=True, data=data_1, header=exp_result_header_1) raw_counts_2 = {'0x2': 5, '0x3': 8} data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2)) - exp_result_header_2 = dict(creg_sizes=[['c0', 2]], memory_slots=2) + exp_result_header_2 = QobjExperimentHeader(creg_sizes=[['c0', 2]], memory_slots=2) exp_result_2 = models.ExperimentResult(shots=13, success=True, data=data_2, header=exp_result_header_2) @@ -150,8 +152,8 @@ def test_memory_counts_header(self): no_header_processed_memory = ['0 0 00', '0 0 00', '0 0 10', '0 0 10', '0 0 10', '0 0 10', '0 0 10'] data = models.ExperimentResultData(memory=raw_memory) - exp_result_header = dict(creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], - memory_slots=4) + exp_result_header = QobjExperimentHeader( + creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]], memory_slots=4) exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, memory=True, data=data, header=exp_result_header) From 8ef78843cc05a54c239f6682b34305c5a1c0cb9e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Apr 2020 12:15:05 -0400 Subject: [PATCH 5/8] Fix lint --- qiskit/result/models.py | 37 ++++++++++++++++++++++++++++++--- qiskit/result/result.py | 19 +++++++++++++++-- qiskit/validation/__init__.py | 14 +++++-------- qiskit/validation/exceptions.py | 23 -------------------- 4 files changed, 56 insertions(+), 37 deletions(-) delete mode 100644 qiskit/validation/exceptions.py diff --git a/qiskit/result/models.py b/qiskit/result/models.py index f9d8dc00dad3..9e0fa9de1571 100644 --- a/qiskit/result/models.py +++ b/qiskit/result/models.py @@ -15,11 +15,10 @@ """Schema and helper models for schema-conformant Results.""" import copy -from types import SimpleNamespace from qiskit.qobj.utils import MeasReturnType, MeasLevel from qiskit.qobj import QobjExperimentHeader -from qiskit.validation.exceptions import ModelValidationError +from qiskit.exceptions import QiskitError class ExperimentResultData: @@ -56,6 +55,11 @@ def __init__(self, counts=None, snapshots=None, memory=None, self.unitary = unitary def to_dict(self): + """Return a dictionary format representation of the ExperimentResultData + + Returns: + dict: The dictionary form of the ExperimentResultData + """ out_dict = {} for field in ['counts', 'snapshots', 'memory', 'statevector', 'unitary']: @@ -65,6 +69,16 @@ def to_dict(self): @classmethod def from_dict(cls, data): + """Create a new ExperimentResultData object from a dictionary. + + Args: + data (dict): A dictionary representing the ExperimentResultData to + create. It will be in the same format as output by + :meth:`to_dict` + Returns: + ExperimentResultData: The ``ExperimentResultData`` object from the + input dictionary. + """ in_data = copy.copy(data) return cls(**in_data) @@ -116,7 +130,7 @@ def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, self.seed = seed if meas_return is not None: if meas_return not in list(MeasReturnType): - raise ModelValidationError('%s not a valid meas_return value') + raise QiskitError('%s not a valid meas_return value') self.meas_return = meas_return self._metadata.update(kwargs) @@ -127,6 +141,11 @@ def __getattr__(self, name): raise AttributeError('Attribute %s is not defined' % name) def to_dict(self): + """Return a dictionary format representation of the ExperimentResult + + Returns: + dict: The dictionary form of the ExperimentResult + """ out_dict = { 'shots': self.shots, 'success': self.success, @@ -146,6 +165,18 @@ def to_dict(self): @classmethod def from_dict(cls, data): + """Create a new ExperimentResult object from a dictionary. + + Args: + data (dict): A dictionary representing the ExperimentResult to + create. It will be in the same format as output by + :meth:`to_dict` + + Returns: + ExperimentResult: The ``ExperimentResult`` object from the input + dictionary. + """ + in_data = copy.copy(data) data_obj = ExperimentResultData.from_dict(in_data.pop('data')) if 'header' in in_data: diff --git a/qiskit/result/result.py b/qiskit/result/result.py index a55c366fc21f..de2be1f34a99 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -15,7 +15,6 @@ """Model for schema-conformant Results.""" import copy -from types import SimpleNamespace from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.pulse.schedule import Schedule @@ -64,6 +63,11 @@ def __init__(self, backend_name, backend_version, qobj_id, job_id, success, self._metadata.update(kwargs) def to_dict(self): + """Return a dictionary format representation of the Result + + Returns: + dict: The dictionary form of the Result + """ out_dict = { 'backend_name': self.backend_name, 'backend_version': self.backend_version, @@ -77,7 +81,7 @@ def to_dict(self): if hasattr(self, 'status'): out_dict['status'] = self.status if hasattr(self, 'header'): - out_dict[field] = self.header.to_dict() + out_dict['header'] = self.header.to_dict() out_dict.update(self._metadata) return out_dict @@ -89,6 +93,17 @@ def __getattr__(self, name): @classmethod def from_dict(cls, data): + """Create a new ExperimentResultData object from a dictionary. + + Args: + data (dict): A dictionary representing the Result to create. It + will be in the same format as output by + :meth:`to_dict`. + Returns: + Result: The ``Result`` object from the input dictionary. + + """ + in_data = copy.copy(data) in_data['results'] = [ ExperimentResult.from_dict(x) for x in in_data.pop('results')] diff --git a/qiskit/validation/__init__.py b/qiskit/validation/__init__.py index 022f097690e7..03d75c951e51 100644 --- a/qiskit/validation/__init__.py +++ b/qiskit/validation/__init__.py @@ -19,16 +19,13 @@ .. currentmodule:: qiskit.validation -Base -==== +JSON Schema +=========== -.. autosummary:: +,, autosummary:: :toctree: ../stubs/ - BaseModel - BaseSchema - bind_schema - ModelTypeValidator + jsonschema.validate_json_against_schema Exceptions ========== @@ -37,6 +34,5 @@ :toctree: ../stubs/ ModelValidationError + jsonschema.SchemaValidationError """ - -from .exceptions import ModelValidationError diff --git a/qiskit/validation/exceptions.py b/qiskit/validation/exceptions.py deleted file mode 100644 index 3fc3b2719a5d..000000000000 --- a/qiskit/validation/exceptions.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exceptions for errors raised by the validation.""" - - -from qiskit.exceptions import QiskitError - - -class ModelValidationError(QiskitError): - """Raised when a sequence subscript is out of range.""" - pass From edabd87d8b4ce3552aba82eec7e861a712d3a729 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 4 May 2020 11:29:44 -0400 Subject: [PATCH 6/8] Fix docstring --- qiskit/validation/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/validation/__init__.py b/qiskit/validation/__init__.py index 03d75c951e51..edf25254b73b 100644 --- a/qiskit/validation/__init__.py +++ b/qiskit/validation/__init__.py @@ -33,6 +33,5 @@ .. autosummary:: :toctree: ../stubs/ - ModelValidationError jsonschema.SchemaValidationError """ From 33be3e2e3821b83b543e1b5073e7824bdfc22bc4 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 4 May 2020 11:48:03 -0400 Subject: [PATCH 7/8] Add release notes --- ...rshmallow-validation-283fa62b3acc51e4.yaml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 releasenotes/notes/remove-marshmallow-validation-283fa62b3acc51e4.yaml diff --git a/releasenotes/notes/remove-marshmallow-validation-283fa62b3acc51e4.yaml b/releasenotes/notes/remove-marshmallow-validation-283fa62b3acc51e4.yaml new file mode 100644 index 000000000000..017bce08d5ea --- /dev/null +++ b/releasenotes/notes/remove-marshmallow-validation-283fa62b3acc51e4.yaml @@ -0,0 +1,24 @@ +--- +upgrade: + - | + The validation components using marshmallow from :mod:`qiskit.validation` + have been removed from terra. Since they are no longer used to build + any objects in terra. + - | + The marshmallow schema classes in :mod:`qiskit.results` have been removed + since they are no longer used by the :class:`qiskit.result.Result` class. + - The output of the :meth:`~qiskit.result.Result.to_dict` method for the + :class:`qiskit.result.Result` class is no longer in a format for direct + JSON serialization. Depending on the content contained in instances of + these classes there may be types that the default JSON encoder doesn't know + how to handle, for example complex numbers or numpy arrays. If you're JSON + serializing the output of the ``to_dict()`` method directly you should + ensure that your JSON encoder can handle these types. + +other: + - | + The :class:`qiskit.result.Result` class which was previously constructed + using the marshmallow library has been refactored to not depend on + marshmallow anymore. This new implementation should be a seemless transition + but some specific behavior that was previously inherited from marshmallow + may not work. Please file issues for any incompatibilities found. From 514d58fef10d2e3db29bfe4c7a71fb1ddf121c9a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 5 May 2020 15:24:02 -0400 Subject: [PATCH 8/8] Fix docs issues from review comments --- qiskit/result/result.py | 3 --- qiskit/validation/__init__.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit/result/result.py b/qiskit/result/result.py index de2be1f34a99..99bfcd294ed3 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -29,9 +29,6 @@ class Result: """Model for Results. - Please note that this class only describes the required fields. For the - full description of the model, please check ``ResultSchema``. - Attributes: backend_name (str): backend name. backend_version (str): backend version, in the form X.Y.Z. diff --git a/qiskit/validation/__init__.py b/qiskit/validation/__init__.py index edf25254b73b..c404971f3a73 100644 --- a/qiskit/validation/__init__.py +++ b/qiskit/validation/__init__.py @@ -22,7 +22,7 @@ JSON Schema =========== -,, autosummary:: +.. autosummary:: :toctree: ../stubs/ jsonschema.validate_json_against_schema