-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Continue on errror #118
Continue on errror #118
Changes from all commits
4f8280a
7c061e3
66a6336
a6f8f5b
9bda486
f07b760
ef60f70
d82855f
b9a81f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,8 @@ | |
import click | ||
from .template_message import TemplateMessage | ||
from .sendmail_client import SendmailClient | ||
from . import exceptions | ||
from .exceptions import MailmergeError | ||
from .log import MailmergeLog | ||
from . import utils | ||
|
||
# Python 2 pathlib support requires backport | ||
|
@@ -55,6 +56,10 @@ | |
type=click.IntRange(1, None), | ||
help="Start on message number INTEGER", | ||
) | ||
@click.option( | ||
"--continue-on-error/--no-continue-on-error", default=False, | ||
help="(Do not) continue program if an error is encountered" | ||
) | ||
@click.option( | ||
"--template", "template_path", | ||
default="mailmerge_template.txt", | ||
|
@@ -79,9 +84,15 @@ | |
type=click.Choice(["colorized", "text", "raw"]), | ||
help="Output format (colorized).", | ||
) | ||
@click.option( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that creating a log by default is reasonable. Maybe we can just call it |
||
"--log", "log_path", | ||
default="", | ||
type=click.Path(), | ||
help="log CSV (not created by default)", | ||
) | ||
def main(sample, dry_run, limit, no_limit, resume, | ||
template_path, database_path, config_path, | ||
output_format): | ||
output_format, continue_on_error, log_path): | ||
""" | ||
Mailmerge is a simple, command line mail merge tool. | ||
|
||
|
@@ -108,37 +119,42 @@ def main(sample, dry_run, limit, no_limit, resume, | |
|
||
# Run | ||
message_num = 1 + start | ||
recipients = [] | ||
try: | ||
log = MailmergeLog(log_path) | ||
template_message = TemplateMessage(template_path) | ||
csv_database = read_csv_database(database_path) | ||
sendmail_client = SendmailClient(config_path, dry_run) | ||
for _, row in enumerate_range(csv_database, start, stop): | ||
sender, recipients, message = template_message.render(row) | ||
sendmail_client.sendmail(sender, recipients, message) | ||
print_bright_white_on_cyan( | ||
">>> message {message_num}" | ||
.format(message_num=message_num), | ||
output_format, | ||
) | ||
print_message(message, output_format) | ||
print_bright_white_on_cyan( | ||
">>> message {message_num} sent" | ||
.format(message_num=message_num), | ||
output_format, | ||
) | ||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this logic can be simplified after merging in the latest upstream develop. That includes a try-except block in the message send part. |
||
recipients = [] | ||
sender, recipients, message = template_message.render(row) | ||
sendmail_client.sendmail(sender, recipients, message) | ||
print_bright_white_on_cyan( | ||
">>> message {message_num}" | ||
.format(message_num=message_num), | ||
output_format, | ||
) | ||
print_message(message, output_format) | ||
print_bright_white_on_cyan( | ||
">>> message {message_num} sent" | ||
.format(message_num=message_num), | ||
output_format, | ||
) | ||
except MailmergeError as error: | ||
log.log(message_num, recipients, error) | ||
print_error( | ||
error, message_num, output_format, continue_on_error | ||
) | ||
else: | ||
if dry_run: | ||
log.log(message_num, recipients, "OK, not sent") | ||
else: | ||
log.log(message_num, recipients, "Sent") | ||
message_num += 1 | ||
except exceptions.MailmergeError as error: | ||
hint_text = '\nHint: "--resume {}"'.format(message_num) | ||
sys.exit( | ||
"Error on message {message_num}\n" | ||
"{error}" | ||
"{hint}" | ||
.format( | ||
message_num=message_num, | ||
error=error, | ||
hint=(hint_text if message_num > 1 else ""), | ||
) | ||
) | ||
except MailmergeError as error: | ||
log.log(message_num, recipients, error) | ||
print_error(error, message_num, output_format, False) | ||
|
||
# Hints for user | ||
if not no_limit: | ||
|
@@ -153,6 +169,9 @@ def main(sample, dry_run, limit, no_limit, resume, | |
"To send messages, use the --no-dry-run option." | ||
) | ||
|
||
# Close log | ||
log.close() | ||
|
||
|
||
if __name__ == "__main__": | ||
# No value for parameter, that's how click works | ||
|
@@ -277,9 +296,9 @@ class StrictExcel(csv.excel): | |
for row in reader: | ||
yield row | ||
except csv.Error as err: | ||
raise exceptions.MailmergeError( | ||
raise MailmergeError( | ||
"{}:{}: {}".format(database_path, reader.line_num, err) | ||
) | ||
) from err | ||
|
||
|
||
def enumerate_range(iterable, start=0, stop=None): | ||
|
@@ -311,6 +330,31 @@ def print_bright_white_on_cyan(string, output_format): | |
print(string) | ||
|
||
|
||
def print_error(error, message_num, output_format, continue_on_error): | ||
"""Print string to stdout, optionally enabling color.""" | ||
if message_num > 1 and not continue_on_error: | ||
hint_text = '\nHint: "--resume {}"'.format(message_num) | ||
else: | ||
hint_text = "" | ||
error_message = ( | ||
"Error on message {message_num}\n" | ||
"{error}" | ||
"{hint}" | ||
.format( | ||
message_num=message_num, | ||
error=error, | ||
hint=hint_text, | ||
) | ||
) | ||
|
||
if output_format == "colorized": | ||
error_message = "\x1b[31m" + error_message + "\x1b(B\x1b[m" | ||
if continue_on_error: | ||
print(error_message, file=sys.stderr) | ||
else: | ||
sys.exit(error_message) | ||
|
||
|
||
def print_message(message, output_format): | ||
"""Print a message with colorized output.""" | ||
assert output_format in ["colorized", "text", "raw"] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
""" | ||
SMTP client reads configuration and sends message. | ||
|
||
Andrew DeOrio <[email protected]> | ||
""" | ||
import os.path | ||
|
||
from .exceptions import MailmergeError | ||
|
||
|
||
class MailmergeLog(): | ||
"""Represents a log file.""" | ||
|
||
def __init__(self, path, dry_run=False): | ||
"""Open a log file for output.""" | ||
self.path = path | ||
self.dry_run = dry_run | ||
try: | ||
if self.path: | ||
if os.path.isfile(self.path): | ||
self.file = open(self.path, "a+") | ||
else: | ||
self.file = open(self.path, "w+") | ||
print("number,email,log", file=self.file) | ||
else: | ||
self.file = None | ||
except MailmergeError as err: | ||
raise MailmergeError( | ||
"{}: {}".format( | ||
self.path, "Unable to open log file" | ||
) | ||
) from err | ||
|
||
def log(self, number, recipients, message): | ||
"""Write a message to a log file.""" | ||
if self.file: | ||
if len(recipients) == 0: | ||
recipients = [""] | ||
try: | ||
for email in recipients: | ||
print( | ||
"{},{},\"{}\"".format( | ||
number, email, message | ||
), | ||
file=self.file | ||
) | ||
except MailmergeError as err: | ||
raise MailmergeError( | ||
"{}: {}".format( | ||
self.path, "Unable to write to logfile" | ||
) | ||
) from err | ||
|
||
def close(self): | ||
"""Close a log file.""" | ||
if self.file: | ||
try: | ||
self.file.close() | ||
self.file = None | ||
except MailmergeError as err: | ||
raise MailmergeError( | ||
"{}: {}".format( | ||
self.path, "Unable to close logfile" | ||
) | ||
) from err |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# coding=utf-8 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More ideas for tests:
|
||
# Python 2 source containing unicode https://www.python.org/dev/peps/pep-0263/ | ||
""" | ||
System tests. | ||
|
||
Andrew DeOrio <[email protected]> | ||
|
||
pytest tmpdir docs: | ||
http://doc.pytest.org/en/latest/tmpdir.html#the-tmpdir-fixture | ||
""" | ||
import textwrap | ||
import sh | ||
|
||
# Python 2 pathlib support requires backport | ||
try: | ||
from pathlib2 import Path | ||
except ImportError: | ||
from pathlib import Path | ||
|
||
# The sh library triggers lot of false no-member errors | ||
# pylint: disable=no-member | ||
|
||
|
||
def test_log(tmpdir): | ||
"""Verify --continue-on-error continues if theere is an error.""" | ||
|
||
# Second attachment | ||
attachment2_path = Path(tmpdir/"attachment2.txt") | ||
attachment2_path.write_text(u"Hello mailmerge\n") | ||
|
||
# Simple template | ||
template_path = Path(tmpdir/"mailmerge_template.txt") | ||
template_path.write_text(textwrap.dedent(u"""\ | ||
TO: {{email}} | ||
FROM: [email protected] | ||
ATTACHMENT: {{attachment}} | ||
|
||
Hello world | ||
""")) | ||
|
||
# Simple database with two entries | ||
database_path = Path(tmpdir/"mailmerge_database.csv") | ||
database_path.write_text(textwrap.dedent(u"""\ | ||
email,attachment | ||
[email protected],attachment1.txt | ||
[email protected],attachment2.txt | ||
""")) | ||
|
||
# Simple unsecure server config | ||
config_path = Path(tmpdir/"mailmerge_server.conf") | ||
config_path.write_text(textwrap.dedent(u"""\ | ||
[smtp_server] | ||
host = open-smtp.example.com | ||
port = 25 | ||
""")) | ||
|
||
# Run mailmerge | ||
log = tmpdir.join('log.csv') | ||
with tmpdir.as_cwd(): | ||
output = sh.mailmerge( | ||
"--no-limit", "--continue-on-error", | ||
"--log", "log.csv" | ||
) | ||
stderr = output.stderr.decode("utf-8") | ||
assert "message 1 sent" not in output | ||
assert "message 2 sent" in output | ||
assert "Error on message 1" in stderr | ||
assert "Attachment not found" in stderr | ||
assert log.read() == textwrap.dedent(u"""\ | ||
number,email,log | ||
1,,"Attachment not found: {}/attachment1.txt" | ||
2,[email protected],"OK, not sent" | ||
""".format(tmpdir)) | ||
|
||
|
||
def test_continue_on_error(tmpdir): | ||
"""Verify --continue-on-error continues if theere is an error.""" | ||
|
||
# Second attachment | ||
attachment2_path = Path(tmpdir/"attachment2.txt") | ||
attachment2_path.write_text(u"Hello mailmerge\n") | ||
|
||
# Simple template | ||
template_path = Path(tmpdir/"mailmerge_template.txt") | ||
template_path.write_text(textwrap.dedent(u"""\ | ||
TO: {{email}} | ||
FROM: [email protected] | ||
ATTACHMENT: {{attachment}} | ||
|
||
Hello world | ||
""")) | ||
|
||
# Simple database with two entries | ||
database_path = Path(tmpdir/"mailmerge_database.csv") | ||
database_path.write_text(textwrap.dedent(u"""\ | ||
email,attachment | ||
[email protected],attachment1.txt | ||
[email protected],attachment2.txt | ||
""")) | ||
|
||
# Simple unsecure server config | ||
config_path = Path(tmpdir/"mailmerge_server.conf") | ||
config_path.write_text(textwrap.dedent(u"""\ | ||
[smtp_server] | ||
host = open-smtp.example.com | ||
port = 25 | ||
""")) | ||
|
||
# Run mailmerge | ||
with tmpdir.as_cwd(): | ||
output = sh.mailmerge("--no-limit", "--continue-on-error") | ||
stderr = output.stderr.decode("utf-8") | ||
assert "message 1 sent" not in output | ||
assert "message 2 sent" in output | ||
assert "Error on message 1" in stderr | ||
assert "Attachment not found" in stderr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would lean towards using Python's standard library logging mechanism over building a custom logger.