Ниже я покажу пару приятных особенностей Python существенно облегчающих жизнь разработчику
Есть задача отправки почтовых уведомлений об изменении статуса заявки. Решение в лоб подталкивает к созданию подобной фунцкии класса Call:
def send_mail(self):
if проверка на реальность объекта
генерация заголовка письма
генерация текста письма
try: обработка исключений
подключени и авторизация на смтп
отправка письма
return True
тут видно как образуется минимум четырёх ступенная лесница, что не очень красиво. При этом код отправки сообщения децентрализуется. По-этому можно почесать голову и пойти другим путём, воспользовавшись особенностям Python
Первым делом я предлагаю создать отдельный класс который реализует отправку сообщений. Полученный класс можно включать в список наследуемых классов, тем самым расширяя функционал класса. Например, я определя класс SendMail и примешиваю его к определению модели Call
class Call(Base.Model, SendMail)
Впрочем, подмешивать этот класс можно к любой другой модели, например к модели пользователя (User), для отправки уведомлений о восстановлении пароля. При этом, с точки зрения пользователя класса, хорошо бы просто не париться с такими глупостями как генерация шаблонного текста и заголовка письма, а так же не думать подключение, пускай это будет скрыто во внутренностях объекта. Для этого необходимо, чтобы базовый класс SendMail мог использовать аттрибуты порождённого класса:
msg = MIMEText(self.obj_mail)
msg['Subject'] = self.obj_subject
это подразумевает то, что в объекте производного класса будут использоваться свойства obj_mail и obj_subject и работать это будет так:
# нахожу заявку в базе
call = Call.query.get_or_404(7777)
# отправляю письмо
call.send()
во время вызова метода send() может происходит генерация шаблонного текста и если такое поведение предусмотрено, то письмо отправляется, если объект не готовился к работе с почтой, то метод просто ничего не делает
Для релизации такого поведения необходимо чтобы базовый класс проверял существование созданных объектом атрибутов obj_mail и obj_subject. Сделать это можно с помощью декоратора к методу send
def __precheck__(fun):
def _wrapper(self):
if getattr(self, 'obj_subject', None) and getattr(self, 'obj_mail', None):
fun(self)
return _wrapper
@__precheck__
def send(self):
Этот декоратор перехватывает вызов метода send объектом производного класса, затем в случае существования среди аттрибутов объекта obj_mail и obj_subject вызывает декорируемую функцию send иначе не делает ничего, то-есть вызов функции send у ненастроенного объекта вернёт обычное None
Следующим шагом необходимо подготовить аттрибуты obj_mail и obj_subject у производного класса, конечно можно инициализировать эти объекты в конструкторе или каком то специальном методе, но можно определить их как динамические аттрибуты с помощью декоратора @property
def final_email(self):
return render_template('final_email.html', call=self )
@property
def obj_subject(self):
if self.id and self.intra:
return 'ДКМ Технологии, заявка #{0} закрыта'.format(self.intra)
@property
def obj_mail(self):
if self.id and self.intra:
return self.final_email()
такое объявление позволит обращаться к функциям obj_mail и obj_subject как к обычным функциям, а значит остаётся свобода имён этих аттрибутов и возможность их диначеского переопределения
Ну и вот вырезки кода поясняющие работу этих механизмов
class SendMail(object):
def __precheck__(fun):
def _wrapper(self):
if getattr(self, 'obj_subject', None) and getattr(self, 'obj_mail', None):
fun(self)
return _wrapper
@__precheck__
def send(self):
msg = MIMEText(self.obj_mail)
msg['Subject'] = self.obj_subject
msg['From'] = application.config.get('MAIL_LOGIN')
msg['To'] = ','.join(application.config.get('TARGET_MAIL'))
try:
with smtplib.SMTP(application.config.get('MAIL_SERVER')) as s:
s.login(application.config.get('MAIL_LOGIN'),
application.config.get('MAIL_PASSWORD')
)
s.sendmail(application.config.get('MAIL_LOGIN'),
application.config.get('TARGET_MAIL'), msg.as_string())s.quit()
return True
except Exception as error:
logging.error("ERROR: ", error)class Call(Base.Model, SendMail):
__tablename__ = 'calls'
mysql_character_set = 'utf8'id = Column(Integer, primary_key=True, autoincrement=True)
intra = Column(String(32), nullable=True, doc='№ заявки по базе ')def final_email(self):
if self.id:
return render_template('final_email.html', call=self )
def send_final_email(self):
return self.send()
@property
def obj_subject(self):
if self.id and self.intra:
return 'ДКМ Технологии, заявка #{0} закрыта'.format(self.intra)@property
def obj_mail(self):
if self.id and self.intra:
return self.final_email()
как видите можно расширить поведение путём передачи аргументом имени шаблона для письма, но мне пока это не нужно.
Комментариев 0