Replace deprecated optparse with argparse - toot - Unnamed repository; edit this file 'description' to name the repository.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit d7701bd2e6beb3a303d4133f685ac685080cf1dd
 (DIR) parent 86f4e1beacd49e803358d22927c00b7a4a1ffc2b
 (HTM) Author: Ivan Habunek <ivan@habunek.com>
       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(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       @@ -5,4 +5,6 @@ build/
        dist/
        tmp/
        .pypirc
       -/.env
       -\ No newline at end of file
       +/.env
       +/.coverage
       +/htmlcov
 (DIR) 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/
 (DIR) 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
 (DIR) 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('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):
                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 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)
        
       -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
 (DIR) 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, 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)
            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", 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:
       -        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(options.visibility))
       -
       -    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, visibility=args.visibility)
        
            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=FileType('rb'))
        
       -    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))