Replace deprecated optparse with argparse

This commit is contained in:
Ivan Habunek 2017-04-16 14:06:16 +02:00
parent 86f4e1beac
commit d7701bd2e6
No known key found for this signature in database
GPG Key ID: CDBD63C43A30BB95
5 changed files with 154 additions and 74 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ dist/
tmp/ tmp/
.pypirc .pypirc
/.env /.env
/.coverage
/htmlcov

View File

@ -12,7 +12,10 @@ dist :
@echo "\nDone." @echo "\nDone."
clean : clean :
rm -rf build dist *.egg-info MANIFEST rm -rf build dist *.egg-info MANIFEST htmlcov
publish : publish :
twine upload dist/* twine upload dist/*
coverage:
py.test --cov=toot --cov-report html tests/

View File

@ -1,3 +1,4 @@
pytest>=3.0.0 pytest-cov~=2.4.0
twine>=1.8.1 pytest~=3.0.0
wheel>=0.29.0 twine~=1.8.1
wheel~=0.29.0

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import pytest import pytest
import requests import requests
import sys
from toot import User, App from toot import User, App
from toot.console import cmd_post_status, ConsoleError from toot.console import print_usage, cmd_post_status, cmd_timeline, cmd_upload
from tests.utils import MockResponse from tests.utils import MockResponse
@ -11,12 +11,19 @@ app = App('https://habunek.com', 'foo', 'bar')
user = User('ivan@habunek.com', 'xxx') user = User('ivan@habunek.com', 'xxx')
def test_post_status_defaults(monkeypatch): def test_print_usagecap(capsys):
print_usage()
out, err = capsys.readouterr()
assert "toot - interact with Mastodon from the command line" in out
def test_post_status_defaults(monkeypatch, capsys):
def mock_prepare(request): def mock_prepare(request):
assert request.method == 'POST' assert request.method == 'POST'
assert request.url == 'https://habunek.com/api/v1/statuses' assert request.url == 'https://habunek.com/api/v1/statuses'
assert request.headers == {'Authorization': 'Bearer xxx'}
assert request.data == { assert request.data == {
'status': '"Hello world"', 'status': 'Hello world',
'visibility': 'public', 'visibility': 'public',
'media_ids[]': None, 'media_ids[]': None,
} }
@ -29,14 +36,17 @@ def test_post_status_defaults(monkeypatch):
monkeypatch.setattr(requests.Request, 'prepare', mock_prepare) monkeypatch.setattr(requests.Request, 'prepare', mock_prepare)
monkeypatch.setattr(requests.Session, 'send', mock_send) monkeypatch.setattr(requests.Session, 'send', mock_send)
sys.argv = ['toot', 'post', '"Hello world"'] cmd_post_status(app, user, ['Hello world'])
cmd_post_status(app, user)
out, err = capsys.readouterr()
assert "Toot posted" in out
def test_post_status_with_options(monkeypatch): def test_post_status_with_options(monkeypatch, capsys):
def mock_prepare(request): def mock_prepare(request):
assert request.method == 'POST' assert request.method == 'POST'
assert request.url == 'https://habunek.com/api/v1/statuses' assert request.url == 'https://habunek.com/api/v1/statuses'
assert request.headers == {'Authorization': 'Bearer xxx'}
assert request.data == { assert request.data == {
'status': '"Hello world"', 'status': '"Hello world"',
'visibility': 'unlisted', 'visibility': 'unlisted',
@ -51,25 +61,80 @@ def test_post_status_with_options(monkeypatch):
monkeypatch.setattr(requests.Request, 'prepare', mock_prepare) monkeypatch.setattr(requests.Request, 'prepare', mock_prepare)
monkeypatch.setattr(requests.Session, 'send', mock_send) monkeypatch.setattr(requests.Session, 'send', mock_send)
sys.argv = ['toot', 'post', '"Hello world"', args = ['"Hello world"', '--visibility', 'unlisted']
'--visibility', 'unlisted']
cmd_post_status(app, user) cmd_post_status(app, user, args)
out, err = capsys.readouterr()
assert "Toot posted" in out
def test_post_status_invalid_visibility(monkeypatch): def test_post_status_invalid_visibility(monkeypatch, capsys):
sys.argv = ['toot', 'post', '"Hello world"', args = ['Hello world', '--visibility', 'foo']
'--visibility', 'foo']
with pytest.raises(ConsoleError) as ex: with pytest.raises(SystemExit):
cmd_post_status(app, user) cmd_post_status(app, user, args)
assert str(ex.value) == "Invalid visibility value given: 'foo'"
out, err = capsys.readouterr()
assert "invalid visibility value: 'foo'" in err
def test_post_status_invalid_media(monkeypatch): def test_post_status_invalid_media(monkeypatch, capsys):
sys.argv = ['toot', 'post', '"Hello world"', args = ['Hello world', '--media', 'does_not_exist.jpg']
'--media', 'does_not_exist.jpg']
with pytest.raises(ConsoleError) as ex: with pytest.raises(SystemExit):
cmd_post_status(app, user) cmd_post_status(app, user, args)
assert str(ex.value) == "File does not exist: does_not_exist.jpg"
out, err = capsys.readouterr()
assert "can't open 'does_not_exist.jpg'" in err
def test_timeline(monkeypatch, capsys):
def mock_get(url, params, headers=None):
assert url == 'https://habunek.com/api/v1/timelines/home'
assert headers == {'Authorization': 'Bearer xxx'}
assert params is None
return MockResponse([{
'account': {
'display_name': 'Frank Zappa',
'username': 'fz'
},
'created_at': '2017-04-12T15:53:18.174Z',
'content': "<p>The computer can't tell you the emotional story. It can give you the exact mathematical design, but what's missing is the eyebrows.</p>",
'reblog': None,
}])
monkeypatch.setattr(requests, 'get', mock_get)
cmd_timeline(app, user, [])
out, err = capsys.readouterr()
assert "The computer can't tell you the emotional story." in out
assert "Frank Zappa @fz" in out
def test_upload(monkeypatch, capsys):
def mock_prepare(request):
assert request.method == 'POST'
assert request.url == 'https://habunek.com/api/v1/media'
assert request.headers == {'Authorization': 'Bearer xxx'}
assert request.files.get('file') is not None
def mock_send(*args):
return MockResponse({
'id': 123,
'url': 'https://bigfish.software/123/456',
'preview_url': 'https://bigfish.software/789/012',
'text_url': 'https://bigfish.software/345/678',
'type': 'image',
})
monkeypatch.setattr(requests.Request, 'prepare', mock_prepare)
monkeypatch.setattr(requests.Session, 'send', mock_send)
cmd_upload(app, user, [__file__])
out, err = capsys.readouterr()
assert "Uploading media" in out
assert __file__ in out

View File

@ -12,7 +12,7 @@ from datetime import datetime
from future.moves.itertools import zip_longest from future.moves.itertools import zip_longest
from getpass import getpass from getpass import getpass
from itertools import chain from itertools import chain
from optparse import OptionParser from argparse import ArgumentParser, FileType
from textwrap import TextWrapper from textwrap import TextWrapper
from .config import save_user, load_user, load_app, save_app, CONFIG_APP_FILE, CONFIG_USER_FILE from .config import save_user, load_user, load_app, save_app, CONFIG_APP_FILE, CONFIG_USER_FILE
@ -131,7 +131,13 @@ def parse_timeline(item):
} }
def cmd_timeline(app, user): def cmd_timeline(app, user, args):
parser = ArgumentParser(prog="toot timeline",
description="Show recent items in your public timeline",
epilog="https://github.com/ihabunek/toot")
args = parser.parse_args(args)
items = timeline_home(app, user) items = timeline_home(app, user)
parsed_items = [parse_timeline(t) for t in items] parsed_items = [parse_timeline(t) for t in items]
@ -141,39 +147,41 @@ def cmd_timeline(app, user):
print("" * 31 + "" + "" * 88) print("" * 31 + "" + "" * 88)
def cmd_post_status(app, user): def visibility(value):
parser = OptionParser(usage="toot post [options] TEXT") if value not in ['public', 'unlisted', 'private', 'direct']:
raise ValueError("Invalid visibility value")
parser.add_option("-m", "--media", dest="media", type="string", return value
help="path to the media file to attach")
parser.add_option("-v", "--visibility", dest="visibility", type="string", default="public",
help='post visibility, either "public" (default), "direct", "private", or "unlisted"')
(options, args) = parser.parse_args() def cmd_post_status(app, user, args):
parser = ArgumentParser(prog="toot post",
description="Post a status text to the timeline",
epilog="https://github.com/ihabunek/toot")
parser.add_argument("text", help="The status text to post.")
parser.add_argument("-m", "--media", type=FileType('rb'),
help="path to the media file to attach")
parser.add_argument("-v", "--visibility", type=visibility, default="public",
help='post visibility, either "public" (default), "direct", "private", or "unlisted"')
if len(args) < 2: args = parser.parse_args(args)
parser.print_help()
raise ConsoleError("No text given")
if options.visibility not in ['public', 'unlisted', 'private', 'direct']: if args.media:
raise ConsoleError("Invalid visibility value given: '{}'".format(options.visibility)) media = do_upload(app, user, args.media)
if options.media:
media = do_upload(app, user, options.media)
media_ids = [media['id']] media_ids = [media['id']]
else: else:
media_ids = None media_ids = None
response = post_status( response = post_status(app, user, args.text, media_ids=media_ids, visibility=args.visibility)
app, user, args[1], media_ids=media_ids, visibility=options.visibility)
print("Toot posted: " + green(response.get('url'))) print("Toot posted: " + green(response.get('url')))
def cmd_auth(app, user): def cmd_auth(app, user, args):
parser = OptionParser(usage='%prog auth') parser = ArgumentParser(prog="toot auth",
parser.parse_args() description="Show login details",
epilog="https://github.com/ihabunek/toot")
parser.parse_args(args)
if app and user: if app and user:
print("You are logged in to " + green(app.base_url)) print("You are logged in to " + green(app.base_url))
@ -185,7 +193,9 @@ def cmd_auth(app, user):
def cmd_login(): def cmd_login():
parser = OptionParser(usage='%prog login') parser = ArgumentParser(prog="toot login",
description="Log into a Mastodon instance",
epilog="https://github.com/ihabunek/toot")
parser.parse_args() parser.parse_args()
app = create_app_interactive() app = create_app_interactive()
@ -194,24 +204,26 @@ def cmd_login():
return app, user return app, user
def cmd_logout(app, user): def cmd_logout(app, user, args):
parser = OptionParser(usage='%prog logout') parser = ArgumentParser(prog="toot logout",
parser.parse_args() description="Log out, delete stored access keys",
epilog="https://github.com/ihabunek/toot")
parser.parse_args(args)
os.unlink(CONFIG_APP_FILE) os.unlink(CONFIG_APP_FILE)
os.unlink(CONFIG_USER_FILE) os.unlink(CONFIG_USER_FILE)
print("You are now logged out") print("You are now logged out")
def cmd_upload(app, user): def cmd_upload(app, user, args):
parser = OptionParser(usage='%prog upload <path_to_media>') parser = ArgumentParser(prog="toot upload",
parser.parse_args() description="Upload an image or video file",
epilog="https://github.com/ihabunek/toot")
parser.add_argument("file", help="Path to the file to upload", type=FileType('rb'))
if len(sys.argv) < 3: args = parser.parse_args(args)
print_error("No status text given")
return
response = do_upload(sys.argv[2]) response = do_upload(app, user, args.file)
print("\nSuccessfully uploaded media ID {}, type '{}'".format( print("\nSuccessfully uploaded media ID {}, type '{}'".format(
yellow(response['id']), yellow(response['type']))) yellow(response['id']), yellow(response['type'])))
@ -220,16 +232,12 @@ def cmd_upload(app, user):
print("Text URL: " + green(response['text_url'])) print("Text URL: " + green(response['text_url']))
def do_upload(app, user, path): def do_upload(app, user, file):
if not os.path.exists(path): print("Uploading media: {}".format(green(file.name)))
raise ConsoleError("File does not exist: " + path) return upload_media(app, user, file)
with open(path, 'rb') as f:
print("Uploading media: {}".format(green(f.name)))
return upload_media(app, user, f)
def run_command(command): def run_command(command, args):
app = load_app() app = load_app()
user = load_user() user = load_user()
@ -238,7 +246,7 @@ def run_command(command):
return cmd_login() return cmd_login()
if command == 'auth': if command == 'auth':
return cmd_auth(app, user) return cmd_auth(app, user, args)
# Commands which require user to be logged in # Commands which require user to be logged in
if not app or not user: if not app or not user:
@ -247,16 +255,16 @@ def run_command(command):
return return
if command == 'logout': if command == 'logout':
return cmd_logout(app, user) return cmd_logout(app, user, args)
if command == 'post': if command == 'post':
return cmd_post_status(app, user) return cmd_post_status(app, user, args)
if command == 'timeline': if command == 'timeline':
return cmd_timeline(app, user) return cmd_timeline(app, user, args)
if command == 'upload': if command == 'upload':
return cmd_upload(app, user) return cmd_upload(app, user, args)
print(red("Unknown command '{}'\n".format(command))) print(red("Unknown command '{}'\n".format(command)))
print_usage() print_usage()
@ -267,11 +275,12 @@ def main():
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
command = sys.argv[1] if len(sys.argv) > 1 else None command = sys.argv[1] if len(sys.argv) > 1 else None
args = sys.argv[2:]
if not command: if not command:
return print_usage() return print_usage()
try: try:
run_command(command) run_command(command, args)
except ConsoleError as e: except ConsoleError as e:
print_error(str(e)) print_error(str(e))