Ejemplo n.º 1
0
class BackStageItem(Item):
    item: Item = attr.ib(validator=[attr.validators.instance_of(Item)])
    incrementor: Callable[[Union[int, float]], Union[int, float]] = attr.ib(
        validators.is_callable()
    )
    decrementor: Callable[[Union[int, float]], Union[int, float]] = attr.ib(
        validators.is_callable()
    )

    @property
    def name(self) -> str:
        return self.item.name

    @property
    def sell_in(self) -> int:
        return self.item.sell_in

    @property
    def quality(self) -> int:
        return self.item.quality

    def update_quality(self):
        if self.item.quality < 50:
            self.item.quality = self.incrementor(self.item.quality)
            if self.item.sell_in < 11:
                if self.item.quality < 50:
                    self.item.quality = self.incrementor(self.item.quality)
            if self.item.sell_in < 6:
                if self.item.quality < 50:
                    self.item.quality = self.incrementor(self.item.quality)
        self.item.sell_in = self.decrementor(self.item.sell_in)
        if self.item.sell_in < 0:
            self.item.quality = 0
        return self.item
Ejemplo n.º 2
0
class EntryExitManager:
    enter = attr.ib(validator=is_callable())
    exit = attr.ib(validator=is_callable())

    def __enter__(self):
        return self.enter()

    def __exit__(self, *args, **kwargs):
        return self.exit()
Ejemplo n.º 3
0
class Assumption:
    name = attrib(type=str, validator=instance_of(str))
    description = attrib(type=str, validator=instance_of(str))
    assumption = attrib(type=callable, validator=is_callable())
    uses = attrib(type=tuple, validator=instance_of(tuple))
    bounded = attrib(type=bool, validator=instance_of(bool))
    metadata = attrib(type=dict, validator=instance_of(dict))
    doc_generator = attrib(type=callable, validator=is_callable())

    @classmethod
    def create(
        cls,
        *,
        assumption: callable,
        name: Union[str, None] = None,
        bounded: Union[bool, None] = None,
        description: Union[str, None] = None,
        uses: Union[tuple, None] = None,
        cache: Union[tuple, None] = None,
        metadata: Union[dict, None] = None,
        doc_generator: Union[callable, None] = None,
    ):
        if name is None:
            name = assumption.__name__
        if bounded is None:
            bounded = False
        if description is None:
            description = ""
        if uses is None:
            uses = tuple(signature(assumption).parameters.keys())
        if metadata is None:
            metadata = {}
        if doc_generator is None:
            doc_generator = assumption_doc_generator
        return cls(
            name=name,
            description=description,
            uses=uses,
            assumption=assumption,
            bounded=bounded,
            metadata=metadata,
            doc_generator=doc_generator,
        )

    @property
    def __signature__(self):
        return signature(self.assumption)

    @property
    def __doc__(self):
        return self.doc_generator(self)

    def __call__(self, *args, **kwargs):
        return self.assumption(*args, **kwargs)
Ejemplo n.º 4
0
 def test_success(self):
     """
     If the value is callable, nothing happens.
     """
     v = is_callable()
     a = simple_attr("test")
     v(None, a, isinstance)
class DenyRegionsClientSupplier(ClientSupplier):
    """AWS KMS client supplier that supplies clients for any region except for the specified regions.

    .. versionadded:: 1.5.0

    :param List[str] denied_regions: Regions to deny
    :param ClientSupplier client_supplier: Client supplier to wrap (optional)
    """

    denied_regions = attr.ib(validator=(deep_iterable(
        member_validator=instance_of(six.string_types)),
                                        value_is_not_a_string))
    _client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier),
                               validator=optional(is_callable()))

    def __call__(self, region_name):
        # type: (Union[None, str]) -> BaseClient
        """Return a client for the requested region.

        :rtype: BaseClient
        :raises UnknownRegionError: if a region is requested that is in ``denied_regions``
        """
        if region_name in self.denied_regions:
            raise UnknownRegionError(
                "Unable to provide client for region '{}'".format(region_name))

        return self._client_supplier(region_name)
Ejemplo n.º 6
0
    def test_fail(self):
        """
        Raise TypeError if the value is not callable.
        """
        v = is_callable()
        a = simple_attr("test")
        with pytest.raises(TypeError) as e:
            v(None, a, None)

        e.match("'test' must be callable")
Ejemplo n.º 7
0
class RagnarosItem(Item):
    item: Item = attr.ib(validator=[attr.validators.instance_of(Item)])
    incrementor: Callable[[Union[int, float]], Union[int, float]] = attr.ib(
        validators.is_callable()
    )
    decrementor: Callable[[Union[int, float]], Union[int, float]] = attr.ib(
        validators.is_callable()
    )

    @property
    def name(self) -> str:
        return self.item.name

    @property
    def sell_in(self) -> int:
        return self.item.sell_in

    @property
    def quality(self) -> int:
        return self.item.quality

    def update_quality(self):
        return self.item
Ejemplo n.º 8
0
    def test_fail(self):
        """
        Raise TypeError if the value is not callable.
        """
        v = is_callable()
        a = simple_attr("test")
        with pytest.raises(TypeError) as e:
            v(None, a, None)

        value = None
        message = "'test' must be callable (got {value} that is a {type_})."
        expected_message = message.format(value=value, type_=value.__class__)

        assert expected_message == e.value.args[0]
        assert value == e.value.args[1]
        assert expected_message == e.value.msg
        assert value == e.value.value
class _AwsKmsDiscoveryKeyring(Keyring):
    """AWS KMS discovery keyring that will attempt to decrypt any AWS KMS encrypted data key.

    This keyring should never be used directly.
    It should only ever be used internally by :class:`AwsKmsKeyring`.

    .. versionadded:: 1.5.0

    :param ClientSupplier client_supplier: Client supplier to use when asking for clients
    :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional)
    """

    _client_supplier = attr.ib(validator=is_callable())
    _grant_tokens = attr.ib(
        default=attr.Factory(tuple),
        validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string),
    )

    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        return encryption_materials

    def on_decrypt(self, decryption_materials, encrypted_data_keys):
        # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
        new_materials = decryption_materials

        for edk in encrypted_data_keys:
            if new_materials.data_encryption_key is not None:
                return new_materials

            if edk.key_provider.provider_id == KEY_NAMESPACE:
                new_materials = _try_aws_kms_decrypt(
                    client_supplier=self._client_supplier,
                    decryption_materials=new_materials,
                    grant_tokens=self._grant_tokens,
                    encrypted_data_key=edk,
                )

        return new_materials
Ejemplo n.º 10
0
class MdParserConfig:
    """Configuration options for the Markdown Parser.

    Note in the sphinx configuration these option names are prepended with ``myst_``
    """

    renderer: str = attr.ib(default="sphinx",
                            validator=in_(["sphinx", "html", "docutils"]))
    commonmark_only: bool = attr.ib(default=False, validator=instance_of(bool))
    dmath_allow_labels: bool = attr.ib(default=True,
                                       validator=instance_of(bool))
    dmath_allow_space: bool = attr.ib(default=True,
                                      validator=instance_of(bool))
    dmath_allow_digits: bool = attr.ib(default=True,
                                       validator=instance_of(bool))
    dmath_double_inline: bool = attr.ib(default=False,
                                        validator=instance_of(bool))

    update_mathjax: bool = attr.ib(default=True, validator=instance_of(bool))

    enable_extensions: Iterable[str] = attr.ib(factory=lambda: ["dollarmath"])

    @enable_extensions.validator
    def check_extensions(self, attribute, value):
        if not isinstance(value, Iterable):
            raise TypeError(f"myst_enable_extensions not iterable: {value}")
        diff = set(value).difference([
            "dollarmath",
            "amsmath",
            "deflist",
            "html_admonition",
            "html_image",
            "colon_fence",
            "smartquotes",
            "replacements",
            "linkify",
            "substitution",
            "tasklist",
        ])
        if diff:
            raise ValueError(f"myst_enable_extensions not recognised: {diff}")

    disable_syntax: Iterable[str] = attr.ib(
        factory=list,
        validator=deep_iterable(instance_of(str), instance_of((list, tuple))),
    )

    # see https://en.wikipedia.org/wiki/List_of_URI_schemes
    url_schemes: Optional[Iterable[str]] = attr.ib(
        default=None,
        validator=optional(
            deep_iterable(instance_of(str), instance_of((list, tuple)))),
    )

    heading_anchors: Optional[int] = attr.ib(default=None,
                                             validator=optional(
                                                 in_([1, 2, 3, 4, 5, 6, 7])))

    heading_slug_func: Optional[Callable[[str], str]] = attr.ib(
        default=None, validator=optional(is_callable()))

    html_meta: Dict[str, str] = attr.ib(
        factory=dict,
        validator=deep_mapping(instance_of(str), instance_of(str),
                               instance_of(dict)),
        repr=lambda v: str(list(v)),
    )

    footnote_transition: bool = attr.ib(default=True,
                                        validator=instance_of(bool))

    substitutions: Dict[str, Union[str, int, float]] = attr.ib(
        factory=dict,
        validator=deep_mapping(instance_of(str), instance_of(
            (str, int, float)), instance_of(dict)),
        repr=lambda v: str(list(v)),
    )

    sub_delimiters: Tuple[str, str] = attr.ib(default=("{", "}"))

    words_per_minute: int = attr.ib(default=200, validator=instance_of(int))

    @sub_delimiters.validator
    def check_sub_delimiters(self, attribute, value):
        if (not isinstance(value, (tuple, list))) or len(value) != 2:
            raise TypeError(
                f"myst_sub_delimiters is not a tuple of length 2: {value}")
        for delim in value:
            if (not isinstance(delim, str)) or len(delim) != 1:
                raise TypeError(
                    f"myst_sub_delimiters does not contain strings of length 1: {value}"
                )

    def as_dict(self, dict_factory=dict) -> dict:
        return attr.asdict(self, dict_factory=dict_factory)
Ejemplo n.º 11
0
class BaseField:
    """
    A base field for all types available at ALFAsim.

    :param caption: Label to be displayed on the right side of the component.

    :param tooltip: Shows a tip, a short piece of text.

    :param Callable enable_expr: Function to evaluate if the component will be enabled or not.

    :param Callable visible_expr: Function to inform if the component will be visible or not.

    .. rubric:: **Caption and Tooltip**:

    Caption is the most basic information that all fields must inform, it will display Label over the right side of
    the component on the ``Model Explorer`` window.

    Tooltips are short pieces of text to reminder/inform the user about some specificity about the property when they
    keep the mouse over the field. Tooltips must be a string and can have HTML tags and Unicode characters  as well.

    :raise TypeError: if the tooltip informed is not a string.

    .. rubric:: Example myplugin.py

    .. code-block:: python

        @data_model(icon='', caption='My Plugin')
        class MyModel:
            my_string_1= String(
                value='String 1',
                caption='My String 1',
                tooltip="Some Text <br> <b> More Information</b>",
            )
            my_string_2 = String(
                value='String 2',
                caption='My String 2',
                tooltip="∩ ∪ ∫ ∬ ∮",
            )

        @alfasim_sdk.hookimpl
        def alfasim_get_data_model_type():
            return [MyModel]

    The images below shows the output from the example above.

    .. image:: /_static/images/api/base_field_caption.png
        :scale: 60%

    .. image:: /_static/images/api/base_field_tootip_1.png
        :scale: 70%

    .. image:: /_static/images/api/base_field_tootip_2.png
        :scale: 70%

    .. _enable-expression-section:

    .. rubric:: **Enable Expression**:

    Accepts a python function that controls either the component will be enabled, or disabled.
    The python function will receive two arguments, an instance of itself (to check local values) and an instance of
    :func:`alfasim_sdk.context.Context` to retrieve information about the application.

    This function must return a boolean, informing True (for enabled) or False (for disabled).

    .. epigraph:: **enabled**:
        The component will handle keyboard and mouse events.

    .. epigraph:: **disabled**:
        The component will not handle events and it will be grayed out.

    .. rubric:: Example myplugin.py

    .. code-block:: python
        :emphasize-lines: 1-2, 11

        def my_check(self, ctx):
            return self.bool_value

        @data_model(icon="", caption="My Plugin")
        class MyModel:
            bool_value = Boolean(value=True, caption="Enabled")
            N_ions = Quantity(
                caption='Number of Ions',
                value=1,
                unit='-',
                enable_expr=my_check,
            )

        @alfasim_sdk.hookimpl
        def alfasim_get_data_model_type():
            return [MyModel]

    The image below shows the ``N_ions`` property disabled, when the property ``bool_value`` is disabled (False)

    .. image:: /_static/images/api/base_field_enable_expr_1.png

    .. image:: /_static/images/api/base_field_enable_expr_2.png


    .. _visible-expression-section:

    .. rubric:: **Visible Expression**:

    Accepts a python function that controls either the component will be visible, or not.
    The python function will receive two arguments, an instance of itself (to check local values) and an instance of
    :func:`alfasim_sdk.context.Context` to retrieve information about the application.

    This function must return a boolean, informing True (for visible) or False (for invisible).

    .. rubric:: Example myplugin.py

    .. code-block:: python
        :emphasize-lines: 1-2, 11

        def my_check(self, ctx):
            return self.bool_value


        @data_model(icon="", caption="My Plugin")
        class MyModel:
            bool_value = Boolean(value=True, caption="Enabled")
            N_ions = Quantity(
                caption="Number of Ions",
                value=1,
                unit="-",
                visible_expr=my_check,
            )


        @alfasim_sdk.hookimpl
        def alfasim_get_data_model_type():
            return [MyModel]


    The image below shows the ``N_ions`` property visible, when the property ``bool_value`` is enabled (True)

    .. image:: /_static/images/api/base_field_visible_expr_1.png

    .. image:: /_static/images/api/base_field_visible_expr_2.png

    .. Development only

        The BaseField class and all others classes that inheritance from BaseField must use kw_only=True for all attributes.
        This is due to the necessity to make enable_expr and visible_expr an optional value and the only way to have
        properties with default values mixed with required properties is with key-word only arguments.
    """

    caption: str = attrib(validator=non_empty_str)
    tooltip: str = attrib(default="", validator=instance_of(str))
    enable_expr: Optional[Callable] = attrib(
        default=None, validator=optional(is_callable())
    )
    visible_expr: Optional[Callable] = attrib(
        default=None, validator=optional(is_callable())
    )
Ejemplo n.º 12
0
class AwsKmsKeyring(Keyring):
    """Keyring that uses AWS Key Management Service (KMS) Customer Master Keys (CMKs) to manage wrapping keys.

    Set ``generator_key_id`` to require that the keyring use that CMK to generate the data key.
    If you do not set ``generator_key_id``, the keyring will not generate a data key.

    Set ``key_ids`` to specify additional CMKs that the keyring will use to encrypt the data key.

    The keyring will attempt to use any CMKs
    identified by CMK ARN in either ``generator_key_id`` or ``key_ids`` on decrypt.

    You can identify CMKs by any `valid key ID`_ for the keyring to use on encrypt,
    but for the keyring to attempt to use them on decrypt
    you MUST specify the CMK ARN.

    If you specify ``is_discovery=True`` the keyring will be a KMS discovery keyring,
    doing nothing on encrypt and attempting to decrypt any AWS KMS-encrypted data key on decrypt.

    .. note::

        You must either set ``is_discovery=True`` or provide key IDs.

    You can use the :class:`ClientSupplier` to customize behavior further,
    such as to provide different credentials for different regions
    or to restrict which regions are allowed.

    See the `AWS KMS Keyring specification`_ for more details.

    .. _AWS KMS Keyring specification:
       https://github.com/awslabs/aws-encryption-sdk-specification/blob/master/framework/kms-keyring.md
    .. _valid key ID:
       https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html#API_GenerateDataKey_RequestSyntax
    .. _discovery mode:
       https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#kms-keyring-discovery

    .. versionadded:: 1.5.0

    :param ClientSupplier client_supplier: Client supplier that provides AWS KMS clients (optional)
    :param bool is_discovery: Should this be a discovery keyring (optional)
    :param str generator_key_id: Key ID of AWS KMS CMK to use when generating data keys (optional)
    :param List[str] key_ids: Key IDs that will be used to encrypt and decrypt data keys (optional)
    :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional)
    """

    _client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier), validator=is_callable())
    _is_discovery = attr.ib(default=False, validator=instance_of(bool))
    _generator_key_id = attr.ib(default=None, validator=optional(instance_of(six.string_types)))
    _key_ids = attr.ib(
        default=attr.Factory(tuple),
        validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string),
    )
    _grant_tokens = attr.ib(
        default=attr.Factory(tuple),
        validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string),
    )

    def __attrs_post_init__(self):
        """Configure internal keyring."""
        key_ids_provided = self._generator_key_id is not None or self._key_ids
        both = key_ids_provided and self._is_discovery
        neither = not key_ids_provided and not self._is_discovery

        if both:
            raise TypeError("is_discovery cannot be True if key IDs are provided")

        if neither:
            raise TypeError("is_discovery cannot be False if no key IDs are provided")

        if self._is_discovery:
            self._inner_keyring = _AwsKmsDiscoveryKeyring(
                client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
            )
            return

        if self._generator_key_id is None:
            generator_keyring = None
        else:
            generator_keyring = _AwsKmsSingleCmkKeyring(
                key_id=self._generator_key_id, client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
            )

        child_keyrings = [
            _AwsKmsSingleCmkKeyring(
                key_id=key_id, client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
            )
            for key_id in self._key_ids
        ]

        self._inner_keyring = MultiKeyring(generator=generator_keyring, children=child_keyrings)

    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        """Generate a data key using generator keyring
        and encrypt it using any available wrapping key in any child keyring.

        :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify.
        :returns: Optionally modified encryption materials.
        :rtype: EncryptionMaterials
        :raises EncryptKeyError: if unable to encrypt data key.
        """
        return self._inner_keyring.on_encrypt(encryption_materials=encryption_materials)

    def on_decrypt(self, decryption_materials, encrypted_data_keys):
        # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
        """Attempt to decrypt the encrypted data keys.

        :param DecryptionMaterials decryption_materials: Decryption materials for keyring to modify.
        :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys.
        :returns: Optionally modified decryption materials.
        :rtype: DecryptionMaterials
        """
        return self._inner_keyring.on_decrypt(
            decryption_materials=decryption_materials, encrypted_data_keys=encrypted_data_keys
        )
Ejemplo n.º 13
0
class ForeachJig:
    """A model runs a WrappedModel or MappedModels for each item in an iterator.

    :param model: The models to call.
    :type model: Union[WrappedModel, MappedModel]
    :param str iterator_name: The name to assign the iterator to be passed (will be used in
        signature of the returned model).
    :param Optional[Tuple] constant_params: The parameter names which will be constant for all
        items in the iterator.
    :param Optional[Callable] success_wrap: An optional function to call upon running the model
        on the items that returned without error (note if none return without error an empty
        list is returned).
    :param Optional[Callable] error_wrap: An optional function to call upon running the model
        on the items that returned with error (note if none return with error an empty list is
        returned).
    :param Optional[Callable] compute: An optional function to be used to call compute or
        return on the modeled objects. This is to be paired with parallel tools such as
        dask.compute or ray.get.
    :param Optional[Dict] compute_kwargs: Optional kwargs to pass into compute.
    """

    model = attrib(
        type=Union[WrappedModel, MappedModel],
        validator=instance_of((WrappedModel, MappedModel,)),
    )
    iterator_name = attrib(type=str, validator=instance_of(str))
    constant_params = attrib(type=Optional[Tuple], validator=instance_of(tuple))
    success_wrap = attrib(type=Optional[Callable], validator=optional(is_callable()))
    error_wrap = attrib(type=Optional[Callable], validator=optional(is_callable()))
    compute = attrib(type=Optional[Callable], validator=optional(is_callable()))
    compute_kwargs = attrib(type=Optional[Dict], validator=instance_of(dict))

    @property
    def __signature__(self):
        return _make_foreach_signature(self.iterator_name, self.constant_params)

    @classmethod
    def create(
        cls,
        model: Union[WrappedModel, MappedModel],
        iterator_name: str,
        constant_params: Optional[Tuple] = None,
        success_wrap: Optional[Callable] = None,
        error_wrap: Optional[Callable] = None,
        compute: Optional[Callable] = None,
        compute_kwargs: Optional[Dict] = None,
    ):
        """A model runs a WrappedModel or MappedModels for each item in an iterator.

        :param model: The models to call.
        :type model: Union[WrappedModel, MappedModel]
        :param str iterator_name: The name to assign the iterator to be passed (will be used in
            signature of the returned model).
        :param Optional[Tuple] constant_params: The parameter names which will be constant for all
            items in the iterator.
        :param Optional[Callable] success_wrap: An optional function to call upon running the model
            on the items that returned without error (note if none return without error an empty
            list is returned).
        :param Optional[Callable] error_wrap: An optional function to call upon running the model
            on the items that returned with error (note if none return with error an empty list is
            returned).
        :param Optional[Callable] compute: An optional function to be used to call compute or
            return on the modeled objects. This is to be paired with parallel tools such as
            dask.compute or ray.get.
        :param Optional[Dict] compute_kwargs: Optional kwargs to pass into compute.
        """

        if constant_params is None:
            constant_params = tuple()

        if compute_kwargs is None:
            compute_kwargs = {}

        return cls(
            model=model,
            iterator_name=iterator_name,
            constant_params=constant_params,
            success_wrap=success_wrap,
            error_wrap=error_wrap,
            compute=compute,
            compute_kwargs=compute_kwargs,
        )

    def __call__(self, **kwargs):
        """Calls the underlying WrappedModel or MappedModel for each item in the named iterator.

        :return: A tuple where the first item are those items that have been successfully ran through
            the underlying models and the second item are those items that failed.
        """
        iterator = kwargs.pop(self.iterator_name)
        if not isinstance(iterator, Iterable):
            raise TypeError("The specified iterator object is not an iterator.")
        output = [self.model(**entry, **kwargs) for entry in iterator]
        if self.compute is not None:
            output = self.compute(output, **self.compute_kwargs)
        successes, errors = [], []
        for result in output:
            (errors if isinstance(result, Error) else successes).append(result)
        if self.success_wrap is not None and len(successes) > 0:
            successes = self.success_wrap(successes)
        if self.error_wrap is not None and len(errors) > 0:
            errors = self.error_wrap(errors)
        return (successes, errors)
Ejemplo n.º 14
0
class WrappedModel:
    """Model wrapper to catch errors when instantiating and running a model.

    :param model: The model to wrap.
    :param tuple iterator_keys: The keys identifying the record that will be passed to the model.
    :param Optional[Tuple] pass_iterator_keys: The iterator keys to pass into the model.
    :param Optional[Callable] parallel_wrap: An optional wrapper to make the model parallel (e.g., dask.delayed).
    :param  Optional[Dict] parallel_kwargs: Optional kwargs to pass to parallel_wrap.

    :return: The output of the wrapped model when calling model.run() when no
        errors occur. If an error occurs during instantiation or running the model
        an Error object is returned.
    """

    model = attrib()
    iterator_keys = attrib(type=tuple, validator=instance_of(tuple))
    pass_iterator_keys = attrib(
        type=Optional[Tuple], kw_only=True, validator=instance_of(tuple), factory=tuple,
    )
    parallel_wrap = attrib(
        type=Optional[Callable],
        default=None,
        kw_only=True,
        validator=optional(is_callable()),
    )
    parallel_kwargs = attrib(
        type=Optional[Dict],
        default=None,
        kw_only=True,
        validator=optional(instance_of(dict)),
    )
    wrapped_model = attrib(default=None, init=False, repr=False)

    def __attrs_post_init__(self):
        object.__setattr__(self, "__signature__", signature(self.model))

    def create_wrapped_model(self):
        def wrapper(**kwargs):
            try:
                excluded_keys = _exclude_iterator_keys(
                    self.iterator_keys, self.pass_iterator_keys
                )
                model_kwargs = {k: v for k, v in kwargs.items() if k not in excluded_keys}
                ret = self.model(**model_kwargs).run()
            except:
                ex_type, ex_value, ex_trace = sys.exc_info()
                key = ({k: kwargs[k] for k in self.iterator_keys},)
                ret = Error.create(key=key, sys_info=sys.exc_info())
            return ret

        if self.parallel_wrap is not None:
            if self.parallel_kwargs is None:
                wrapper = self.parallel_wrap(wrapper)
            else:
                wrapper = self.parallel_wrap(wrapper, **self.parallel_kwargs)

        object.__setattr__(self, "wrapped_model", wrapper)

    def __call__(self, **kwargs):
        if self.wrapped_model is None:
            self.create_wrapped_model()
        return self.wrapped_model(**kwargs)
Ejemplo n.º 15
0
class _AwsKmsSingleCmkKeyring(Keyring):
    """AWS KMS keyring that only works with a single AWS KMS CMK.

    This keyring should never be used directly.
    It should only ever be used internally by :class:`AwsKmsKeyring`.

    .. versionadded:: 1.5.0

    :param str key_id: CMK key ID
    :param ClientSupplier client_supplier: Client supplier to use when asking for clients
    :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional)
    """

    _key_id = attr.ib(validator=instance_of(six.string_types))
    _client_supplier = attr.ib(validator=is_callable())
    _grant_tokens = attr.ib(
        default=attr.Factory(tuple),
        validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string),
    )

    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        trace_info = MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=self._key_id)
        new_materials = encryption_materials
        try:
            if new_materials.data_encryption_key is None:
                plaintext_key, encrypted_key = _do_aws_kms_generate_data_key(
                    client_supplier=self._client_supplier,
                    key_name=self._key_id,
                    encryption_context=new_materials.encryption_context,
                    algorithm=new_materials.algorithm,
                    grant_tokens=self._grant_tokens,
                )
                new_materials = new_materials.with_data_encryption_key(
                    data_encryption_key=plaintext_key,
                    keyring_trace=KeyringTrace(wrapping_key=trace_info, flags=_GENERATE_FLAGS),
                )
            else:
                encrypted_key = _do_aws_kms_encrypt(
                    client_supplier=self._client_supplier,
                    key_name=self._key_id,
                    plaintext_data_key=new_materials.data_encryption_key,
                    encryption_context=new_materials.encryption_context,
                    grant_tokens=self._grant_tokens,
                )
        except Exception:  # pylint: disable=broad-except
            # We intentionally WANT to catch all exceptions here
            message = "Unable to generate or encrypt data key using {}".format(trace_info)
            _LOGGER.exception(message)
            raise EncryptKeyError(message)

        return new_materials.with_encrypted_data_key(
            encrypted_data_key=encrypted_key, keyring_trace=KeyringTrace(wrapping_key=trace_info, flags=_ENCRYPT_FLAGS)
        )

    def on_decrypt(self, decryption_materials, encrypted_data_keys):
        # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
        new_materials = decryption_materials

        for edk in encrypted_data_keys:
            if new_materials.data_encryption_key is not None:
                return new_materials

            if (
                edk.key_provider.provider_id == KEY_NAMESPACE
                and edk.key_provider.key_info.decode("utf-8") == self._key_id
            ):
                new_materials = _try_aws_kms_decrypt(
                    client_supplier=self._client_supplier,
                    decryption_materials=new_materials,
                    grant_tokens=self._grant_tokens,
                    encrypted_data_key=edk,
                )

        return new_materials
Ejemplo n.º 16
0
 def test_repr(self):
     """
     Returned validator has a useful `__repr__`.
     """
     v = is_callable()
     assert "<is_callable validator>" == repr(v)
Ejemplo n.º 17
0
class DefaultInputHandler(InputHandler):
    """The default input handler.

    Inputs are collected from the command line.
    Secrets are saved to Secrets Manager.
    Parameters are saved to Parameter Store.

    :param stack_namer: Callable that returns the stack name
    :param botocore_session: Pre-configured botocore session (optional)
    """

    _stack_namer: Callable[[], str] = attr.ib(validator=is_callable())
    _botocore_session: botocore.session.Session = attr.ib(
        default=attr.Factory(botocore.session.Session),
        validator=instance_of(botocore.session.Session))
    _cache: Optional[CloudFormationPhysicalResourceCache] = None

    def __attrs_post_init__(self):
        """Initialize all AWS SDK clients."""
        boto3_session = boto3.session.Session(
            botocore_session=self._botocore_session)
        self._secrets_manager = boto3_session.client("secretsmanager")
        self._parameter_store = boto3_session.client("ssm")
        self._cloudformation = boto3_session.client("cloudformation")

    @property
    def cache(self):
        """Lazily create the physical resource cache and return it for use.

        This is necessary because the resources do not exist yet when we create this handler
        (needed for collecting inputs)
        but will exist by the time we need to save those inputs.

        :returns: Cache resource
        """
        if self._cache is not None:
            return self._cache

        self._cache = CloudFormationPhysicalResourceCache(
            client=self._cloudformation,
            stack_name=self._stack_namer()  # pylint: disable=not-callable
        )
        return self._cache

    @staticmethod
    def _input_prompt(value: Input) -> str:
        """Generate the input prompt message for an input.

        :param value: Input for which to create input prompt
        :returns: Formatted input prompt message
        """
        return os.linesep.join((value.description, f"{value.name}: ")).lstrip()

    def collect_secret(self, secret: Input):
        """Collect a secret input value from the user via the CLI.

        :param Input secret: Input to collect from user
        """
        secret.value = getpass.getpass(prompt=self._input_prompt(secret))

    @staticmethod
    def _assert_input_set(value: Input):
        """Verify that an input has a value set.

        :param value: Input to verify
        :raises ValueError: if value is not set
        """
        if value.value is None:
            raise ValueError(f'Value for input "{value.name}" is not set.')

    def save_secret(self, secret: Input):
        """Save a secret input value to Secrets Manager.

        :param secret: Input to save
        """
        _LOGGER.debug('Saving secret value for input "%s"', secret.name)
        self._assert_input_set(secret)
        secret_id = self.cache.physical_resource_name(secret.resource_name())
        self._secrets_manager.update_secret(SecretId=secret_id,
                                            SecretString=secret.value)

    def collect_parameter(self, parameter: Input):
        """Collect a non-secret input value from the user via the CLI.

        :param parameter: Input to collect from user
        """
        parameter.value = input(self._input_prompt(parameter))

    def save_parameter(self, parameter: Input):
        """Save a non-secret input value to Parameter Store.

        :param parameter: Input to save
        """
        _LOGGER.debug('Saving parameter value for input "%s"', parameter.name)
        self._assert_input_set(parameter)
        parameter_name = self.cache.physical_resource_name(
            parameter.resource_name())
        parameter.version = self._parameter_store.put_parameter(
            Name=parameter_name,
            Type="String",
            Value=parameter.value,
            Overwrite=True)