# Path: notifications_utils/template.py
# class SubjectMixin():
# 
#     def __init__(
#         self,
#         template,
#         values=None,
#         **kwargs
#     ):
#         self._subject = template['subject']
#         super().__init__(template, values, **kwargs)
# 
#     @property
#     def subject(self):
#         return Markup(Take(Field(
#             self._subject,
#             self.values,
#             html='escape',
#             redact_missing_personalisation=self.redact_missing_personalisation,
#         )).then(
#             do_nice_typography
#         ).then(
#             normalise_whitespace
#         ))
# 
#     @property
#     def placeholders(self):
#         return get_placeholders(self._subject) | super().placeholders
# 
# class Template(ABC):
# 
#     encoding = "utf-8"
# 
#     def __init__(
#         self,
#         template,
#         values=None,
#         redact_missing_personalisation=False,
#     ):
#         if not isinstance(template, dict):
#             raise TypeError('Template must be a dict')
#         if values is not None and not isinstance(values, dict):
#             raise TypeError('Values must be a dict')
#         if template.get('template_type') != self.template_type:
#             raise TypeError(
#                 f'Cannot initialise {self.__class__.__name__} '
#                 f'with {template.get("template_type")} template_type'
#             )
#         self.id = template.get("id", None)
#         self.name = template.get("name", None)
#         self.content = template["content"]
#         self.values = values
#         self._template = template
#         self.redact_missing_personalisation = redact_missing_personalisation
# 
#     def __repr__(self):
#         return "{}(\"{}\", {})".format(self.__class__.__name__, self.content, self.values)
# 
#     @abstractmethod
#     def __str__(self):
#         pass
# 
#     @property
#     def content_with_placeholders_filled_in(self):
#         return str(Field(
#             self.content,
#             self.values,
#             html='passthrough',
#             redact_missing_personalisation=self.redact_missing_personalisation,
#             markdown_lists=True,
#         )).strip()
# 
#     @property
#     def values(self):
#         if hasattr(self, '_values'):
#             return self._values
#         return {}
# 
#     @values.setter
#     def values(self, value):
#         if not value:
#             self._values = {}
#         else:
#             placeholders = InsensitiveDict.from_keys(self.placeholders)
#             self._values = InsensitiveDict(value).as_dict_with_keys(
#                 self.placeholders | set(
#                     key for key in value.keys()
#                     if InsensitiveDict.make_key(key) not in placeholders.keys()
#                 )
#             )
# 
#     @property
#     def placeholders(self):
#         return get_placeholders(self.content)
# 
#     @property
#     def missing_data(self):
#         return list(
#             placeholder for placeholder in self.placeholders
#             if self.values.get(placeholder) is None
#         )
# 
#     @property
#     def additional_data(self):
#         return self.values.keys() - self.placeholders
# 
#     def get_raw(self, key, default=None):
#         return self._template.get(key, default)
# 
#     def compare_to(self, new):
#         return TemplateChange(self, new)
# 
#     @property
#     def content_count(self):
#         return len(self.content_with_placeholders_filled_in)
# 
#     def is_message_empty(self):
# 
#         if not self.content:
#             return True
# 
#         if not self.content.startswith('((') or not self.content.endswith('))'):
#             # If the content doesn’t start or end with a placeholder we
#             # can guarantee it’s not empty, no matter what
#             # personalisation has been provided.
#             return False
# 
#         return self.content_count == 0
# 
#     def is_message_too_long(self):
#         return False


from unittest.mock import patch
from notifications_utils.template import SubjectMixin, Template
import pytest




class ConcreteImplementation():
    template_type = None

    # Can’t instantiate and test templates unless they implement __str__
    def __str__(self):
        pass



# Path: notifications_utils/template.py
# class SubjectMixin():
# 
#     def __init__(
#         self,
#         template,
#         values=None,
#         **kwargs
#     ):
#         self._subject = template['subject']
#         super().__init__(template, values, **kwargs)
# 
#     @property
#     def subject(self):
#         return Markup(Take(Field(
#             self._subject,
#             self.values,
#             html='escape',
#             redact_missing_personalisation=self.redact_missing_personalisation,
#         )).then(
#             do_nice_typography
#         ).then(
#             normalise_whitespace
#         ))
# 
#     @property
#     def placeholders(self):
#         return get_placeholders(self._subject) | super().placeholders
# 
# class Template(ABC):
# 
#     encoding = "utf-8"
# 
#     def __init__(
#         self,
#         template,
#         values=None,
#         redact_missing_personalisation=False,
#     ):
#         if not isinstance(template, dict):
#             raise TypeError('Template must be a dict')
#         if values is not None and not isinstance(values, dict):
#             raise TypeError('Values must be a dict')
#         if template.get('template_type') != self.template_type:
#             raise TypeError(
#                 f'Cannot initialise {self.__class__.__name__} '
#                 f'with {template.get("template_type")} template_type'
#             )
#         self.id = template.get("id", None)
#         self.name = template.get("name", None)
#         self.content = template["content"]
#         self.values = values
#         self._template = template
#         self.redact_missing_personalisation = redact_missing_personalisation
# 
#     def __repr__(self):
#         return "{}(\"{}\", {})".format(self.__class__.__name__, self.content, self.values)
# 
#     @abstractmethod
#     def __str__(self):
#         pass
# 
#     @property
#     def content_with_placeholders_filled_in(self):
#         return str(Field(
#             self.content,
#             self.values,
#             html='passthrough',
#             redact_missing_personalisation=self.redact_missing_personalisation,
#             markdown_lists=True,
#         )).strip()
# 
#     @property
#     def values(self):
#         if hasattr(self, '_values'):
#             return self._values
#         return {}
# 
#     @values.setter
#     def values(self, value):
#         if not value:
#             self._values = {}
#         else:
#             placeholders = InsensitiveDict.from_keys(self.placeholders)
#             self._values = InsensitiveDict(value).as_dict_with_keys(
#                 self.placeholders | set(
#                     key for key in value.keys()
#                     if InsensitiveDict.make_key(key) not in placeholders.keys()
#                 )
#             )
# 
#     @property
#     def placeholders(self):
#         return get_placeholders(self.content)
# 
#     @property
#     def missing_data(self):
#         return list(
#             placeholder for placeholder in self.placeholders
#             if self.values.get(placeholder) is None
#         )
# 
#     @property
#     def additional_data(self):
#         return self.values.keys() - self.placeholders
# 
#     def get_raw(self, key, default=None):
#         return self._template.get(key, default)
# 
#     def compare_to(self, new):
#         return TemplateChange(self, new)
# 
#     @property
#     def content_count(self):
#         return len(self.content_with_placeholders_filled_in)
# 
#     def is_message_empty(self):
# 
#         if not self.content:
#             return True
# 
#         if not self.content.startswith('((') or not self.content.endswith('))'):
#             # If the content doesn’t start or end with a placeholder we
#             # can guarantee it’s not empty, no matter what
#             # personalisation has been provided.
#             return False
# 
#         return self.content_count == 0
# 
#     def is_message_too_long(self):
#         return False


# Path: tests/test_base_template.py
from unittest.mock import patch
from notifications_utils.template import SubjectMixin, Template
import pytest





class ConcreteImplementation():
    template_type = None

    # Can’t instantiate and test templates unless they implement __str__
    def __str__(self):
        pass



class ConcreteTemplate(ConcreteImplementation, Template):