def _get_student_email(basedir, course, person, student=None): if student is None: student = person targets = None _LOG.debug('construct student grade email about {} for {}'.format( student, student)) else: targets = [person] _LOG.debug('construct student grade email about {} for {}'.format( student, person)) emails = list( _student_email(basedir=basedir, author=course.robot, course=course, student=student, targets=targets, old=True)) if len(emails) == 0: if targets is None: text = ( "We don't have any of your grades on file for this course.") else: text = ("We don't have any grades for {} on file for this course." ).format(student.name) message = _pgp_mime.encodedMIMEText(text) message['Subject'] = 'No grades for {}'.format(student.alias()) raise _Response(message=message, complete=True) elif len(emails) > 1: raise NotImplementedError(emails) email, callback = emails[0] # callback records notification, but don't bother here return email
def load_attachment(filename, encoding='us-ascii'): mimetype,content_encoding = _mimetypes.guess_type(filename) if mimetype is None or content_encoding is not None: mimetype = 'application/octet-stream' maintype,subtype = mimetype.split('/', 1) _pgp_mime.LOG.info('loading attachment {} as {} ({})'.format( filename, mimetype, content_encoding)) if maintype == 'text': text = read_file(filename=filename, encoding=encoding) attachment = _pgp_mime.encodedMIMEText(text) del attachment['content-disposition'] else: data = open(filename, 'rb').read() if maintype == 'application': attachment = _MIMEApplication(data, subtype) elif maintype == 'audio': attachment = _MIMEAudio(data) elif maintype == 'image': attachment = _MIMEImage(data) else: attachment = _MIMENonMultipary(maintype, subtype) attachment.set_payload(data, _encode_base64) attachment.add_header( 'Content-Disposition', 'attachment', filename=filename) return attachment
def load_attachment(filename, encoding='us-ascii'): mimetype, content_encoding = _mimetypes.guess_type(filename) if mimetype is None or content_encoding is not None: mimetype = 'application/octet-stream' maintype, subtype = mimetype.split('/', 1) _pgp_mime.LOG.info('loading attachment {} as {} ({})'.format( filename, mimetype, content_encoding)) if maintype == 'text': text = read_file(filename=filename, encoding=encoding) attachment = _pgp_mime.encodedMIMEText(text) del attachment['content-disposition'] else: data = open(filename, 'rb').read() if maintype == 'application': attachment = _MIMEApplication(data, subtype) elif maintype == 'audio': attachment = _MIMEAudio(data) elif maintype == 'image': attachment = _MIMEImage(data) else: attachment = _MIMENonMultipary(maintype, subtype) attachment.set_payload(data, _encode_base64) attachment.add_header('Content-Disposition', 'attachment', filename=filename) return attachment
def _get_student_email(basedir, course, person, student=None): if student is None: student = person targets = None _LOG.debug('construct student grade email about {} for {}'.format( student, student)) else: targets = [person] _LOG.debug('construct student grade email about {} for {}'.format( student, person)) emails = list(_student_email( basedir=basedir, author=course.robot, course=course, student=student, targets=targets, old=True)) if len(emails) == 0: if targets is None: text = ( "We don't have any of your grades on file for this course." ) else: text = ( "We don't have any grades for {} on file for this course." ).format(student.name) message = _pgp_mime.encodedMIMEText(text) message['Subject'] = 'No grades for {}'.format(student.alias()) raise _Response(message=message, complete=True) elif len(emails) > 1: raise NotImplementedError(emails) email,callback = emails[0] # callback records notification, but don't bother here return email
def run(basedir, course, message, person, subject, max_late=0, dry_run=None, **kwargs): """ >>> from pgp_mime.email import encodedMIMEText >>> from ..test.course import StubCourse >>> from . import Response >>> course = StubCourse() >>> person = list( ... course.course.find_people(email='*****@*****.**'))[0] >>> message = encodedMIMEText('The answer is 42.') >>> message['Message-ID'] = '<*****@*****.**>' >>> message['Received'] = ( ... 'from smtp.home.net (smtp.home.net [123.456.123.456]) ' ... 'by smtp.mail.uu.edu (Postfix) with ESMTP id 5BA225C83EF ' ... 'for <*****@*****.**>; Sun, 09 Oct 2011 11:50:46 -0400 (EDT)') >>> subject = '[submit] assignment 1' >>> try: ... run(basedir=course.basedir, course=course.course, message=message, ... person=person, subject=subject, max_late=0) ... except Response as e: ... print('respond with:') ... print(e.message.as_string()) ... # doctest: +ELLIPSIS, +REPORT_UDIFF respond with: Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline Subject: Received Assignment 1 submission <BLANKLINE> We received your submission for Assignment 1 on .... >>> course.cleanup() """ time = _message_time(message=message) assignment = _get_assignment(course=course, subject=subject) assignment_path = _assignment_path(basedir, assignment, person) _save_local_message_copy( msg=message, person=person, assignment_path=assignment_path, dry_run=dry_run) _extract_mime(message=message, output=assignment_path, dry_run=dry_run) _check_late( basedir=basedir, assignment=assignment, person=person, time=time, max_late=max_late, dry_run=dry_run) if time: time_str = 'on {}'.format(_formatdate(time)) else: time_str = 'at an unknown time' message = _pgp_mime.encodedMIMEText(( 'We received your submission for {} {}.' ).format( assignment.name, time_str)) message['Subject'] = 'Received {} submission'.format(assignment.name) raise _Response(message=message)
def test_smtp(smtp, author, targets, msg=None): """Test the SMTP connection by sending a message to `target` """ if msg is None: msg = _pgp_mime.encodedMIMEText('Success!') msg['Date'] = _email_utils.formatdate() msg['From'] = author msg['Reply-to'] = msg['From'] msg['To'] = ', '.join(targets) msg['Subject'] = 'Testing pygrader SMTP connection' _LOG.info('send test message to SMTP server') smtp.send_message(msg=msg)
def construct_response(author, targets, subject, text, original, cc=None): r"""Build a multipart/mixed response email using `Person` instances >>> from pygrader.model.person import Person as Person >>> student = Person(name='Джон Доу', emails=['*****@*****.**']) >>> assistant = Person(name='Jill', emails=['*****@*****.**']) >>> cc = [assistant] >>> msg = construct_text_email(author=student, targets=[assistant], ... subject='Assignment 1 submission', text='Bla bla bla...') >>> rsp = construct_response(author=assistant, targets=[student], ... subject='Received assignment 1 submission', text='3 hours late', ... original=msg) >>> print(rsp.as_string()) # doctest: +REPORT_UDIFF, +ELLIPSIS Content-Type: multipart/mixed; boundary="===============...==" MIME-Version: 1.0 Date: ... From: Jill <*****@*****.**> Reply-to: Jill <*****@*****.**> To: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> Subject: Received assignment 1 submission <BLANKLINE> --===============...== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline <BLANKLINE> 3 hours late --===============...== Content-Type: message/rfc822 MIME-Version: 1.0 <BLANKLINE> Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline Date: ... From: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> Reply-to: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> To: Jill <*****@*****.**> Subject: Assignment 1 submission <BLANKLINE> Bla bla bla... --===============...==-- """ message = _MIMEMultipart('mixed') message.attach(_pgp_mime.encodedMIMEText(text)) message.attach(_MIMEMessage(original)) return construct_email( author=author, targets=targets, subject=subject, message=message, cc=cc)
def _get_student_submission_email(basedir, course, person, assignments, student): _LOG.debug('construct student submission email about {} {} for {}'.format( student, assignments, person)) subject = '{} assignment submissions for {}'.format( course.name, student.name) text = '{}:\n * {}\n'.format(subject, '\n * '.join(a.name for a in assignments)) message = _MIMEMultipart('mixed') message.attach(_pgp_mime.encodedMIMEText(text)) for assignment in assignments: grade = course.grade(student=student, assignment=assignment) if grade is not None: text = '{} grade: {}\n'.format(assignment.name, grade.points) if grade.comment: text += '\n{}\n'.format(grade.comment) message.attach(_pgp_mime.encodedMIMEText(text)) assignment_path = _assignment_path(basedir, assignment, student) mpath = _os_path.join(assignment_path, 'mail') try: mbox = _mailbox.Maildir(mpath, factory=None, create=False) except _mailbox.NoSuchMailboxError as e: pass else: messages = [] for key, msg in mbox.items(): subpath = mbox._lookup(key) if subpath.endswith('.gitignore'): _LOG.debug('skipping non-message {}'.format(subpath)) continue messages.append(msg) messages.sort(key=_message_time) for msg in messages: message.attach(_MIMEMessage(msg)) return _construct_email(author=course.robot, targets=[person], subject=subject, message=message)
def _get_student_submission_email( basedir, course, person, assignments, student): _LOG.debug('construct student submission email about {} {} for {}'.format( student, assignments, person)) subject = '{} assignment submissions for {}'.format( course.name, student.name) text = '{}:\n * {}\n'.format( subject, '\n * '.join(a.name for a in assignments)) message = _MIMEMultipart('mixed') message.attach(_pgp_mime.encodedMIMEText(text)) for assignment in assignments: grade = course.grade(student=student, assignment=assignment) if grade is not None: text = '{} grade: {}\n'.format(assignment.name, grade.points) if grade.comment: text += '\n{}\n'.format(grade.comment) message.attach(_pgp_mime.encodedMIMEText(text)) assignment_path = _assignment_path(basedir, assignment, student) mpath = _os_path.join(assignment_path, 'mail') try: mbox = _mailbox.Maildir(mpath, factory=None, create=False) except _mailbox.NoSuchMailboxError as e: pass else: messages = [] for key,msg in mbox.items(): subpath = mbox._lookup(key) if subpath.endswith('.gitignore'): _LOG.debug('skipping non-message {}'.format(subpath)) continue messages.append(msg) messages.sort(key=_message_time) for msg in messages: message.attach(_MIMEMessage(msg)) return _construct_email( author=course.robot, targets=[person], subject=subject, message=message)
def construct_text_email(author, targets, subject, text, cc=None): r"""Build a text/plain email using `Person` instances >>> from pygrader.model.person import Person as Person >>> author = Person(name='Джон Доу', emails=['*****@*****.**']) >>> targets = [Person(name='Jill', emails=['*****@*****.**'])] >>> cc = [Person(name='H.D.', emails=['*****@*****.**'])] >>> msg = construct_text_email(author, targets, cc=cc, ... subject='Once upon a time', text='Bla bla bla...') >>> print(msg.as_string()) # doctest: +REPORT_UDIFF, +ELLIPSIS Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline Date: ... From: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> Reply-to: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> To: Jill <*****@*****.**> Cc: "H.D." <*****@*****.**> Subject: Once upon a time <BLANKLINE> Bla bla bla... With unicode text: >>> msg = construct_text_email(author, targets, cc=cc, ... subject='Once upon a time', text='Funky ✉.') >>> print(msg.as_string()) # doctest: +REPORT_UDIFF, +ELLIPSIS Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline Date: ... From: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> Reply-to: =?utf-8?b?0JTQttC+0L0g0JTQvtGD?= <*****@*****.**> To: Jill <*****@*****.**> Cc: "H.D." <*****@*****.**> Subject: Once upon a time <BLANKLINE> RnVua3kg4pyJLg== <BLANKLINE> """ message = _pgp_mime.encodedMIMEText(text) return construct_email( author=author, targets=targets, subject=subject, message=message, cc=cc)
'--output', action='store_const', const=True, help="don't mail the generated message, print it to stdout instead") parser.add_argument( '-V', '--verbose', default=0, action='count', help='increment verbosity') args = parser.parse_args() if args.verbose: _pgp_mime.LOG.setLevel(max( _logging.DEBUG, _pgp_mime.LOG.level - 10*args.verbose)) header_text = read_file(filename=args.header_file, encoding=args.encoding) header = _pgp_mime.header_from_text(header_text) body_text = read_file(filename=args.body_file, encoding=args.encoding) body = _pgp_mime.encodedMIMEText(body_text) if args.attachment: b = _MIMEMultipart() b.attach(body) body = b _mimetypes.init() for attachment in args.attachment: body.attach(load_attachment( filename=attachment, encoding=args.encoding)) config = _configparser.ConfigParser() config.read(args.config) client_params = _pgp_mime.get_client_params(config) if args.sign_as: signers = [args.sign_as]
def run(basedir, course, message, person, subject, max_late=0, dry_run=None, **kwargs): """ >>> from pgp_mime.email import encodedMIMEText >>> from ..test.course import StubCourse >>> from . import Response >>> course = StubCourse() >>> person = list( ... course.course.find_people(email='*****@*****.**'))[0] >>> message = encodedMIMEText('The answer is 42.') >>> message['Message-ID'] = '<*****@*****.**>' >>> message['Received'] = ( ... 'from smtp.home.net (smtp.home.net [123.456.123.456]) ' ... 'by smtp.mail.uu.edu (Postfix) with ESMTP id 5BA225C83EF ' ... 'for <*****@*****.**>; Sun, 09 Oct 2011 11:50:46 -0400 (EDT)') >>> subject = '[submit] assignment 1' >>> try: ... run(basedir=course.basedir, course=course.course, message=message, ... person=person, subject=subject, max_late=0) ... except Response as e: ... print('respond with:') ... print(e.message.as_string()) ... # doctest: +ELLIPSIS, +REPORT_UDIFF respond with: Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline Subject: Received Assignment 1 submission <BLANKLINE> We received your submission for Assignment 1 on .... >>> course.cleanup() """ time = _message_time(message=message) assignment = _get_assignment(course=course, subject=subject) assignment_path = _assignment_path(basedir, assignment, person) _save_local_message_copy(msg=message, person=person, assignment_path=assignment_path, dry_run=dry_run) _extract_mime(message=message, output=assignment_path, dry_run=dry_run) _check_late(basedir=basedir, assignment=assignment, person=person, time=time, max_late=max_late, dry_run=dry_run) if time: time_str = 'on {}'.format(_formatdate(time)) else: time_str = 'at an unknown time' message = _pgp_mime.encodedMIMEText( ('We received your submission for {} {}.').format( assignment.name, time_str)) message['Subject'] = 'Received {} submission'.format(assignment.name) raise _Response(message=message)
parser.add_argument('-V', '--verbose', default=0, action='count', help='increment verbosity') args = parser.parse_args() if args.verbose: _pgp_mime.LOG.setLevel( max(_logging.DEBUG, _pgp_mime.LOG.level - 10 * args.verbose)) header_text = read_file(filename=args.header_file, encoding=args.encoding) header = _pgp_mime.header_from_text(header_text) body_text = read_file(filename=args.body_file, encoding=args.encoding) body = _pgp_mime.encodedMIMEText(body_text) if args.attachment: b = _MIMEMultipart() b.attach(body) body = b _mimetypes.init() for attachment in args.attachment: body.attach( load_attachment(filename=attachment, encoding=args.encoding)) config = _configparser.ConfigParser() config.read(args.config) client_params = _pgp_mime.get_client_params(config) if args.sign_as: signers = [args.sign_as]