text: import hashlib
  import json
  from six import string_types
  
  class LogicalIdGenerator(object):
  
      # NOTE: Changing the length of the hash will change backwards compatibility. This will break the stability contract
      #       given by this class
      HASH_LENGTH = 10
  
      def __init__(self, prefix, data_obj=None):
          """
          Generate logical IDs for resources that are stable, deterministic and platform independent
  
          :param prefix: Prefix for the logicalId
          :param data_obj: Data object to trigger new changes on. If set to None, this is ignored
          """
  
          data_str = ""
          if data_obj:
              data_str = self._stringify(data_obj)
  
          self._prefix = prefix
          self.data_str = data_str
  
  
      def gen(self):
          """
          Generate stable LogicalIds based on the prefix and given data. This method ensures that the logicalId is
          deterministic and stable based on input prefix & data object. In other words:
  
              logicalId changes *if and only if* either the `prefix` or `data_obj` changes
  
          Internally we simply use a SHA1 of the data and append to the prefix to create the logicalId.
  
          NOTE: LogicalIDs are how CloudFormation identifies a resource. If this ID changes, CFN will delete and
                create a new resource. This can be catastrophic for most resources. So it is important to be *always*
                backwards compatible here.
  
  
          :return: LogicalId that can be used to construct resources
          :rtype string
          """
  
          data_hash = self.get_hash()
          return "{prefix}{hash}".format(prefix=self._prefix, hash=data_hash)
  
      def get_hash(self, length=HASH_LENGTH):
          """
          Generate and return a hash of data that can be used as suffix of logicalId
  
          :return: Hash of data if it was present
          :rtype string
          """
  
          data_hash = ""
          if self.data_str:
              data_hash = hashlib.sha1(bytes(self.data_str)).hexdigest()
  
          return data_hash[:length]
  
      def _stringify(self, data):
          """
          Stable, platform & language-independent stringification of a data with basic Python type.
  
          We use JSON to dump a string instead of `str()` method in order to be language independent.
  
          :param data: Data to be stringified. If this is one of JSON native types like string, dict, array etc, it will
                       be properly serialized. Otherwise this method will throw a TypeError for non-JSON serializable
                       objects
          :return: string representation of the dictionary
          :rtype string
          """
          if isinstance(data, string_types):
              return data
  
          # Get the most compact dictionary (separators) and sort the keys recursively to get a stable output
          return json.dumps(data, separators=(',', ':'), sort_keys=True)
  