Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
lib-py-b2b / change.py
Size: Mime:
from datetime import datetime
from enum import Enum
from typing import Any, Optional

from boto3.dynamodb.conditions import Attr, Or
from dateutil.parser import isoparse
from py_aws_util.logging import log_data

from lib_b2b.persistent import Persistable
from lib_b2b.policy import Policy
from .base import BaseClass
import abc
import logging
from os import environ

logger = logging.getLogger(__name__)


class ChangeRecord(BaseClass, Persistable):
    def __init__(self, before: Any, after: Any, description: str, when: datetime, who: str = 'the System'):
        self.before = before
        self.after = after
        self.description = description
        self.when = when
        self.who = who

    @staticmethod
    def from_dict(data: dict):
        return ChangeRecord(before=data['before'], after=data['after'], description=data['description'],
                            when=isoparse(data['when']), who=data['who'])

    def as_dict(self) -> dict:
        return {
            'before': self.before,
            'after': self.after,
            'description': self.description,
            'when': self.when.isoformat(),
            'who': self.who
        }

    def __str__(self):
        return f"{self.description} by {self.who} on {self.when.strftime('%b %d, %Y at %H:%M:%S')}"


class ChangeRequest:
    def __init__(self, on_behalf_of: str, policy: Policy = None):
        self.policy = policy
        self.changeable_object = None
        self.on_behalf_of = on_behalf_of

    @abc.abstractmethod
    def permitted(self, changeable_object: 'Changeable') -> bool:
        raise NotImplementedError

    @abc.abstractmethod
    def will_change(self, changeable_object: 'Changeable') -> bool:
        """
        Compares the existing data to the change request to determine if there has been a change
        :return: True/False
        :rtype: bool
        """
        raise NotImplementedError

    @abc.abstractmethod
    def apply(self, changeable_object: 'Changeable'):
        raise NotImplementedError


class Changeable:
    @abc.abstractmethod
    def record(self, change_record: ChangeRecord):
        raise NotImplementedError

    @abc.abstractmethod
    def supported_change_request_types(self) -> [ChangeRequest]:
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def changeable_identity(self):
        raise NotImplementedError

    def change(self, changes: [ChangeRequest]) -> 'Changeable':
        for change in changes:
            logger.info(f"Processing change of type: {type(change).__name__}.",
                        extra=log_data(change_type=type(change).__name__, changeable_id=str(self.changeable_identity)))
            change.apply(self)
            logger.info(f"Change of type: {type(change).__name__} applied.",
                        extra=log_data(change_type=type(change).__name__, changeable_id=str(self.changeable_identity)))
            return self


class ChangeDataProvider(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def version(self) -> Optional[str]:
        """
        returns the version of the change data.  In most cases this is an iso formatted date.
        If is is an ISO formatted date, it should be UTC to ensure consistency.
        :return: String representation of the version.  If None is returned, we will override the
                        data value ignoring the version.
        :rtype: str
        """

    @abc.abstractmethod
    def data(self) -> dict:
        """
        The request data
        :return: dictionary of the change request data
        :rtype: dict
        """
        raise NotImplementedError