Skip to content

Instantly share code, notes, and snippets.

@danizen
Created July 5, 2022 16:02
Show Gist options
  • Save danizen/a3b5e3f8514be90b796f298dfb52f99e to your computer and use it in GitHub Desktop.
Save danizen/a3b5e3f8514be90b796f298dfb52f99e to your computer and use it in GitHub Desktop.
base management command to run a PL/SQL report
from . import BaseReportCommand
class Command(BaseReportCommand):
help = 'Report on records with both .....'
LOGFILE = 'report_output.log'
TEMPLATE = 'report_template.sql'
SUBJECT = 'Record has ERMS data and FER flag'
RECIPIENTS = [
'myusername',
'yourusername',
# ...
]
ATTACHMENTS = [
'path_to_an_attachment.txt'
]
import logging
import os
import re
import socket
import time
from copy import copy
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.core.mail import EmailMessage
from django.core.management import call_command
from django.template.loader import render_to_string
LOG = logging.getLogger(__name__)
# TODO: create emails if there is an error - add FAILED to subject
class SendEmailMixin:
SUBJECT = None
RECIPIENTS = []
ATTACHMENTS = []
def get_attachments(self, log_path):
attachments = copy([str(path) for path in self.ATTACHMENTS])
if log_path:
attachments.append(log_path)
return attachments
def attach_files(self, email, attachments):
have_serials_dir = os.path.isdir(settings.REPORT_DIR)
for path in attachments:
filename = os.path.basename(path)
try:
with open(path) as f:
content = f.read()
email.attach(filename, content, 'text/plain')
except FileNotFoundError as e:
if have_serials_dir:
raise e
def should_email(self, to_addresses):
return len(to_addresses) > 0
def get_message(self, subject):
hostname = socket.gethostname()
message = f'{subject}\nreport attachments and log from {hostname}\n'
return message
def send_email(self, log_path, from_address, to_addresses, subject=None):
if subject is None:
subject = f'{settings.SCRIPT_BANNER}: {self.SUBJECT}'
message = self.get_message(subject)
email = EmailMessage(subject, message, from_address, to_addresses)
attachments = self.get_attachments(log_path)
self.attach_files(email, attachments)
email.send()
class BaseReportCommand(BaseCommand, SendEmailMixin):
help = 'This is a generic report command; subclass of this report should add help'
LOGFILE = None
TEMPLATE = None
def add_arguments(self, parser):
# Options
parser.add_argument('--log', '-l', metavar='PATH', default=None,
help='Path to the log file to be saved')
parser.add_argument('--email', '-e', metavar='ADDRESSES', nargs='+', default=self.RECIPIENTS,
help='Who wants email about the report')
parser.add_argument('--from', '-f', metavar='ADDRESS', default='seradm@nlm.nih.gov',
help='Change the address the email comes from')
def get_context_data(self, **kwargs):
return {k: v for k, v in kwargs.items()}
def run_report(self, log_path):
# Validate there is a template
if self.TEMPLATE is None:
raise CommandError('must specify a TEMPLATE for the report')
# create a pipe and turn the output file descriptor into a Python IO object
infd, outfd = os.pipe()
outs = open(outfd, 'w')
# write a little SQL script (from a template) to the output
template_path = f'sqlproc/{self.TEMPLATE}'
context_data = self.get_context_data(log_path=log_path)
rendered_string = render_to_string(template_path, context_data)
without_extra_lines = re.sub('\n\n+', '\n', rendered_string.lstrip())
outs.write(without_extra_lines)
outs.close()
# tell user what we will do
LOG.info("sending template '%s' for %s to sqlplus", template_path, self.SUBJECT)
# make stdin come from the pipe and run sqlplus
os.dup2(infd, 0)
stime = time.perf_counter()
call_command('dbshell')
elapsed = time.perf_counter() - stime
# Django has control again and can send email based on the script's log
# stdin still comes from the closed pipe,
LOG.info('sqlplus finished in %.2f seconds', elapsed)
def handle(self, *args, **options):
log_path = options.get('log')
to_addresses = options.get('email')
from_address = options.get('from')
# append '@nlm.nih.gov' to bare names
to_addresses = [smtp if '@' in smtp else smtp+'@nlm.nih.gov'
for smtp in to_addresses]
# Add default log path
if log_path is None and self.LOGFILE:
log_path = os.path.join(os.environ.get('DJANGO_LOG_DIR'), self.LOGFILE)
# Validate there is a subject
if self.SUBJECT is None:
raise CommandError('must specify a SUBJECT for the report')
self.run_report(log_path)
if self.should_email(to_addresses):
self.send_email(log_path, from_address, to_addresses)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment