Replace deprecated optparse with argparse - toot - Unnamed repository; edit thi… | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit d7701bd2e6beb3a303d4133f685ac685080cf1dd | |
parent 86f4e1beacd49e803358d22927c00b7a4a1ffc2b | |
Author: Ivan Habunek <[email protected]> | |
Date: Sun, 16 Apr 2017 14:06:16 +0200 | |
Replace deprecated optparse with argparse | |
Diffstat: | |
.gitignore | 5 +++-- | |
Makefile | 5 ++++- | |
requirements-dev.txt | 8 ++++---- | |
tests/test_console.py | 109 ++++++++++++++++++++++++------- | |
toot/console.py | 103 +++++++++++++++++-------------- | |
5 files changed, 154 insertions(+), 76 deletions(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -5,4 +5,6 @@ build/ | |
dist/ | |
tmp/ | |
.pypirc | |
-/.env | |
-\ No newline at end of file | |
+/.env | |
+/.coverage | |
+/htmlcov | |
diff --git a/Makefile b/Makefile | |
@@ -12,7 +12,10 @@ dist : | |
@echo "\nDone." | |
clean : | |
- rm -rf build dist *.egg-info MANIFEST | |
+ rm -rf build dist *.egg-info MANIFEST htmlcov | |
publish : | |
twine upload dist/* | |
+ | |
+coverage: | |
+ py.test --cov=toot --cov-report html tests/ | |
diff --git a/requirements-dev.txt b/requirements-dev.txt | |
@@ -1,3 +1,4 @@ | |
-pytest>=3.0.0 | |
-twine>=1.8.1 | |
-wheel>=0.29.0 | |
-\ No newline at end of file | |
+pytest-cov~=2.4.0 | |
+pytest~=3.0.0 | |
+twine~=1.8.1 | |
+wheel~=0.29.0 | |
diff --git a/tests/test_console.py b/tests/test_console.py | |
@@ -1,9 +1,9 @@ | |
+# -*- coding: utf-8 -*- | |
import pytest | |
import requests | |
-import sys | |
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 | |
@@ -11,12 +11,19 @@ app = App('https://habunek.com', 'foo', 'bar') | |
user = User('[email protected]', '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): | |
assert request.method == 'POST' | |
assert request.url == 'https://habunek.com/api/v1/statuses' | |
+ assert request.headers == {'Authorization': 'Bearer xxx'} | |
assert request.data == { | |
- 'status': '"Hello world"', | |
+ 'status': 'Hello world', | |
'visibility': 'public', | |
'media_ids[]': None, | |
} | |
@@ -29,14 +36,17 @@ def test_post_status_defaults(monkeypatch): | |
monkeypatch.setattr(requests.Request, 'prepare', mock_prepare) | |
monkeypatch.setattr(requests.Session, 'send', mock_send) | |
- sys.argv = ['toot', 'post', '"Hello world"'] | |
- cmd_post_status(app, user) | |
+ cmd_post_status(app, user, ['Hello world']) | |
+ | |
+ 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): | |
assert request.method == 'POST' | |
assert request.url == 'https://habunek.com/api/v1/statuses' | |
+ assert request.headers == {'Authorization': 'Bearer xxx'} | |
assert request.data == { | |
'status': '"Hello world"', | |
'visibility': 'unlisted', | |
@@ -51,25 +61,80 @@ def test_post_status_with_options(monkeypatch): | |
monkeypatch.setattr(requests.Request, 'prepare', mock_prepare) | |
monkeypatch.setattr(requests.Session, 'send', mock_send) | |
- sys.argv = ['toot', 'post', '"Hello world"', | |
- '--visibility', 'unlisted'] | |
+ args = ['"Hello world"', '--visibility', 'unlisted'] | |
+ | |
+ cmd_post_status(app, user, args) | |
+ | |
+ out, err = capsys.readouterr() | |
+ assert "Toot posted" in out | |
+ | |
+ | |
+def test_post_status_invalid_visibility(monkeypatch, capsys): | |
+ args = ['Hello world', '--visibility', 'foo'] | |
+ | |
+ with pytest.raises(SystemExit): | |
+ cmd_post_status(app, user, args) | |
+ | |
+ out, err = capsys.readouterr() | |
+ assert "invalid visibility value: 'foo'" in err | |
+ | |
+ | |
+def test_post_status_invalid_media(monkeypatch, capsys): | |
+ args = ['Hello world', '--media', 'does_not_exist.jpg'] | |
+ | |
+ with pytest.raises(SystemExit): | |
+ cmd_post_status(app, user, args) | |
- cmd_post_status(app, user) | |
+ out, err = capsys.readouterr() | |
+ assert "can't open 'does_not_exist.jpg'" in err | |
-def test_post_status_invalid_visibility(monkeypatch): | |
- sys.argv = ['toot', 'post', '"Hello world"', | |
- '--visibility', 'foo'] | |
+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 | |
- with pytest.raises(ConsoleError) as ex: | |
- cmd_post_status(app, user) | |
- assert str(ex.value) == "Invalid visibility value given: 'foo'" | |
+ 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… | |
+ '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) | |
-def test_post_status_invalid_media(monkeypatch): | |
- sys.argv = ['toot', 'post', '"Hello world"', | |
- '--media', 'does_not_exist.jpg'] | |
+ cmd_upload(app, user, [__file__]) | |
- with pytest.raises(ConsoleError) as ex: | |
- cmd_post_status(app, user) | |
- assert str(ex.value) == "File does not exist: does_not_exist.jpg" | |
+ out, err = capsys.readouterr() | |
+ assert "Uploading media" in out | |
+ assert __file__ in out | |
diff --git a/toot/console.py b/toot/console.py | |
@@ -12,7 +12,7 @@ from datetime import datetime | |
from future.moves.itertools import zip_longest | |
from getpass import getpass | |
from itertools import chain | |
-from optparse import OptionParser | |
+from argparse import ArgumentParser, FileType | |
from textwrap import TextWrapper | |
from .config import save_user, load_user, load_app, save_app, CONFIG_APP_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 time… | |
+ epilog="https://github.com/ihabunek/toot") | |
+ | |
+ args = parser.parse_args(args) | |
+ | |
items = timeline_home(app, user) | |
parsed_items = [parse_timeline(t) for t in items] | |
@@ -141,39 +147,41 @@ def cmd_timeline(app, user): | |
print("─" * 31 + "┼" + "─" * 88) | |
-def cmd_post_status(app, user): | |
- parser = OptionParser(usage="toot post [options] TEXT") | |
+def visibility(value): | |
+ if value not in ['public', 'unlisted', 'private', 'direct']: | |
+ raise ValueError("Invalid visibility value") | |
- parser.add_option("-m", "--media", dest="media", type="string", | |
- help="path to the media file to attach") | |
+ return value | |
- parser.add_option("-v", "--visibility", dest="visibility", type="string", … | |
- help='post visibility, either "public" (default), "direc… | |
- (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), "dir… | |
- if len(args) < 2: | |
- parser.print_help() | |
- raise ConsoleError("No text given") | |
+ args = parser.parse_args(args) | |
- if options.visibility not in ['public', 'unlisted', 'private', 'direct']: | |
- raise ConsoleError("Invalid visibility value given: '{}'".format(optio… | |
- | |
- if options.media: | |
- media = do_upload(app, user, options.media) | |
+ if args.media: | |
+ media = do_upload(app, user, args.media) | |
media_ids = [media['id']] | |
else: | |
media_ids = None | |
- response = post_status( | |
- app, user, args[1], media_ids=media_ids, visibility=options.visibility) | |
+ response = post_status(app, user, args.text, media_ids=media_ids, visibili… | |
print("Toot posted: " + green(response.get('url'))) | |
-def cmd_auth(app, user): | |
- parser = OptionParser(usage='%prog auth') | |
- parser.parse_args() | |
+def cmd_auth(app, user, args): | |
+ parser = ArgumentParser(prog="toot auth", | |
+ description="Show login details", | |
+ epilog="https://github.com/ihabunek/toot") | |
+ parser.parse_args(args) | |
if app and user: | |
print("You are logged in to " + green(app.base_url)) | |
@@ -185,7 +193,9 @@ def cmd_auth(app, user): | |
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() | |
app = create_app_interactive() | |
@@ -194,24 +204,26 @@ def cmd_login(): | |
return app, user | |
-def cmd_logout(app, user): | |
- parser = OptionParser(usage='%prog logout') | |
- parser.parse_args() | |
+def cmd_logout(app, user, args): | |
+ parser = ArgumentParser(prog="toot logout", | |
+ 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_USER_FILE) | |
print("You are now logged out") | |
-def cmd_upload(app, user): | |
- parser = OptionParser(usage='%prog upload <path_to_media>') | |
- parser.parse_args() | |
+def cmd_upload(app, user, args): | |
+ parser = ArgumentParser(prog="toot upload", | |
+ 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=FileTy… | |
- if len(sys.argv) < 3: | |
- print_error("No status text given") | |
- return | |
+ args = parser.parse_args(args) | |
- response = do_upload(sys.argv[2]) | |
+ response = do_upload(app, user, args.file) | |
print("\nSuccessfully uploaded media ID {}, type '{}'".format( | |
yellow(response['id']), yellow(response['type']))) | |
@@ -220,16 +232,12 @@ def cmd_upload(app, user): | |
print("Text URL: " + green(response['text_url'])) | |
-def do_upload(app, user, path): | |
- if not os.path.exists(path): | |
- raise ConsoleError("File does not exist: " + path) | |
- | |
- with open(path, 'rb') as f: | |
- print("Uploading media: {}".format(green(f.name))) | |
- return upload_media(app, user, f) | |
+def do_upload(app, user, file): | |
+ print("Uploading media: {}".format(green(file.name))) | |
+ return upload_media(app, user, file) | |
-def run_command(command): | |
+def run_command(command, args): | |
app = load_app() | |
user = load_user() | |
@@ -238,7 +246,7 @@ def run_command(command): | |
return cmd_login() | |
if command == 'auth': | |
- return cmd_auth(app, user) | |
+ return cmd_auth(app, user, args) | |
# Commands which require user to be logged in | |
if not app or not user: | |
@@ -247,16 +255,16 @@ def run_command(command): | |
return | |
if command == 'logout': | |
- return cmd_logout(app, user) | |
+ return cmd_logout(app, user, args) | |
if command == 'post': | |
- return cmd_post_status(app, user) | |
+ return cmd_post_status(app, user, args) | |
if command == 'timeline': | |
- return cmd_timeline(app, user) | |
+ return cmd_timeline(app, user, args) | |
if command == 'upload': | |
- return cmd_upload(app, user) | |
+ return cmd_upload(app, user, args) | |
print(red("Unknown command '{}'\n".format(command))) | |
print_usage() | |
@@ -267,11 +275,12 @@ def main(): | |
logging.basicConfig(level=logging.DEBUG) | |
command = sys.argv[1] if len(sys.argv) > 1 else None | |
+ args = sys.argv[2:] | |
if not command: | |
return print_usage() | |
try: | |
- run_command(command) | |
+ run_command(command, args) | |
except ConsoleError as e: | |
print_error(str(e)) |