Created
July 5, 2022 16:02
-
-
Save danizen/a3b5e3f8514be90b796f298dfb52f99e to your computer and use it in GitHub Desktop.
base management command to run a PL/SQL report
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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='[email protected]', | |
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