Using Python Click within a class - python

I've got an old flashcards app that I made that I've repurposed for some aws cert study. I wrote this years ago and overhauled that code, and one of those changes involved using click over argparse, but I'm having some issues using click. My code is below (minus a couple of extra functions):
The idea is that load_deck() needs to read the file argument. The way that I was trying to do it is pass load_deck() the file argument through main()
Finally, running this code states that within the Flashcards().main() call, main() is missing a positional argument self. I think this is a problem with the way I'm using click.
import os
import random
import sys
import click
import keyboard
import ruamel.yaml
class Flashcards:
"""
pass
"""
def __init__(self):
self.deck = ruamel.yaml.YAML()
self.card = ['key', 'value']
def load_deck(self, file):
"""
pass
"""
with open(file, mode='r') as deck:
self.deck = self.deck.load(deck)
#click.command()
#click.option('-f', '--file', default='aws.yaml', help='specifies yaml file to use')
#click.option('-r', '--reverse', default=False, help='displays values prompting user to guess keys')
def main(self, file, reverse):
"""
pass
"""
self.load_deck(file)
if reverse is True:
self.deck = [
[card[1], card[0]
] for card in self.deck]
os.system('clear')
print('Press [SPACEBAR] to advance. Exit at anytime with [CTRL] + [C]\n'
'There are {} cards in your deck.\n'.format(len(self.deck)))
try:
while True:
self.read_card()
self.flip_card()
except KeyboardInterrupt:
# removes '^C' from terminal output
os.system('clear')
sys.exit(0)
if __name__ == "__main__":
Flashcards().main()
The program reads a yaml file in the following format (it was previously a spanish flashcard app):
bajar: to descend
borrar: to erase
contestar: to answer

Click isn't naturally designed to work this way, see for example this issue which also includes some workarounds by folks in the comments if you want to go that route.
In your case, you could just yoink main out of the class and have it instantiate Flashcards for you:
#click.etc.
def main(file, reverse):
f = Flashcards()
f.load_deck(file)
# And so on, using 'f' instead of 'self'

I found this workaround:
import click
class Stub:
pass
viking = click.make_pass_decorator(Stub,ensure=True)
#click.group()
#viking
#click.pass_context
def cli(ctx,_):
ctx.obj = Viking()
class Viking(Stub):
def __init__(self):
self.name = "Thor"
#cli.command()
#viking
def plunder(self):
print(f"{self.name} attacks")
#cli.command()
#viking
def sail(self):
print("whoosh")
cli()

Related

pass argument when opening another python file as a module?

I'm remaking a game in python and the menu file and the game file are separate. The menu imports the game file as a module and calls it when requested. However I want variables that determine the sound and difficulty to be passed through from the menu file to the game file, as these options are chosen by the user in the menu.
I'm not sure how to do this at all, so here is how i import and call the python game file:
import SpaceInvaders
SpaceInvaders.SpaceInvaders().mainLoop()
and I want to pass the arguments 'sound' and 'difficulty' through, whose values are strings.
also i call the main loop function but the variables need to be usable in the SpaceInvaders class so i can assign them to self.sound and self.difficulty, they aren't used in the main loop.
If you want to pass sound and difficulty to the mainLoop of SpaceInvaders, then make it so that mainLoop takes them as arguments, and then you will be able to send them:
SpaceInvaders.SpaceInvaders().mainLoop(sound, difficulty)
To answer the additional question "That's the issue, I don't want them in main loop, I want them in the SpaceInvaders class :(" - do this:
SpaceInvaders.SpaceInvaders(sound, difficulty).mainLoop()
example usage:
python somegame.py hello args --difficulty medium
how to use sys.argv create an example:
echo 'import sys;print(sys.argv)' >> test.py
python test.py okay okay --diff med
output:
['1.py', 'okay', 'okay', '--diff', 'med']
very basic example using sys.argv:
import sys
import SpaceInvaders
print(sys.argv)
# step through using pdb
# see: https://docs.python.org/3/library/pdb.html
import pdb;pdb.set_trace() #print(dir(), dir(SpaceInvaders))
# 'n' enter, 'n' enter
# set self.stuff
# create the class
invade = SpaceInvaders.SpaceInvaders(sys.argv)
# pass argv to mainLoop
invade.mainLoop(sys.argv)
and in SpaceInvaders.py:
class SpaceInvaders(object):
def __init__(self, args):
print(args)
self.difficulty = # some var from args
import pdb;pdb.set_trace()
def mainLoop(self, args):
print(args)
if you want to get really fancy checkout:
https://github.com/pallets/click
https://github.com/google/python-fire
and of course checkout examples of argparse

preferred method to dynamically change the urwid.MainLoop widget

I was looking over a bit of code rooted in urwid:
import urwid
from functools import partial
from random import randint
class State(object):
def __init__(self, main_widget):
self.main_widget = main_widget
def handle_keystroke(app_state, key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
else:
loop.widget = urwid.Filler(urwid.Button('new rand int:' + str(randint(0, 100))))
app_state = State(urwid.Filler(urwid.Button('original widget')))
callback = partial(handle_keystroke, app_state)
loop = urwid.MainLoop(app_state.main_widget, unhandled_input=callback)
loop.run()
and noticed that loop is referenced in the function unhandled_input before it's defined. Furthermore, it's not passed as a parameter, it's just hard coded into the function by name. 1) Why is this possible, and: 2) is there a clearer alternative? It is difficult to do otherwise, as there is a circular dependencies of loop, app_state and callback.
I'm not sure how much of your sample code represents the original code, but it looks like you may want to get familiar with the technique of using urwid's custom widgets wrapping text widgets, as shown in the answer with an example widget that displays a text content one line at the time.
Here is an example of writing something similar to the sample code you provided, in a design that fits urwid and Python a bit better:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
import urwid
from random import randint
class RandomNumberWidget(urwid.WidgetWrap):
def __init__(self):
self.random_number = None
self.text_widget = urwid.Text(u'')
super(RandomNumberWidget, self).__init__(self.text_widget)
def roll(self):
self.random_number = randint(0, 100)
self.update()
def update(self):
"""Update UI
"""
if self.random_number is None:
self.text_widget.set_text('No number set')
else:
self.text_widget.set_text('Random number: %s' % self.random_number)
class App(object):
def __init__(self):
self.random_number_widget = RandomNumberWidget()
top_message = 'Press any key to get a random number, or q to quit\n\n\n'
widget = urwid.Pile([
urwid.Padding(urwid.Text(top_message),
'center', width=('relative', len(top_message))),
self.random_number_widget,
])
self.widget = urwid.Filler(widget, 'top')
def play(self):
self.random_number_widget.roll()
def play_or_exit(self, key):
if key in ('q', 'Q', 'esc'):
raise urwid.ExitMainLoop()
app.play()
if __name__ == '__main__':
app = App()
loop = urwid.MainLoop(app.widget, unhandled_input=app.play_or_exit)
loop.run()
Depending also on what you actually want to do, it could make sense to make the custom widgets respond to the keyboard events, instead of doing it all in the global handler (which is totally fine for simple programs, IMO).
When python compiles a function, left-hand-side variables that are the target of assignment are treated as local and the rest are global. loop is not assigned, so when python runs loop.widget = urwid.Filler(...), it knows that loop is not a local variable and it will look the name up in the module's namespace.
Module namespaces are dynamic, so as long as loop = urwid.MainLoop(app_state.main_widget, unhandled_input=callback) runs before the lookup, loop is created and it works. Since the callback can't be executed until loop.run(), loop will be defined.
This is one of the classic risks of singletons and global state. Its not always easy to make sure the resource is created before it is used.

python click usage of standalone_mode

This question is about the Python Click library.
I want click to gather my commandline arguments. When gathered, I want to reuse these values. I dont want any crazy chaining of callbacks, just use the return value. By default, click disables using the return value and calls sys.exit().
I was wondering how to correctly invoke standalone_mode (http://click.pocoo.org/5/exceptions/#what-if-i-don-t-want-that) in case I want to use the decorator style. The above linked doc only shows the usage when (manually) creating Commands using click.
Is it even possible? A minimal example is shown below. It illustrates how click calls sys.exit() directly after returning from gatherarguments
import click
#click.command()
#click.option('--name', help='Enter Name')
#click.pass_context
def gatherarguments(ctx, name):
return ctx
def usectx(ctx):
print("Name is %s" % ctx.params.name)
if __name__ == '__main__':
ctx = gatherarguments()
print(ctx) # is never called
usectx(ctx) # is never called
$ python test.py --name Your_Name
I would love this to be stateless, meaning, without any click.group functionality - I just want the results, without my application exiting.
Just sending standalone_mode as a keyword argument worked for me:
from __future__ import print_function
import click
#click.command()
#click.option('--name', help='Enter Name')
#click.pass_context
def gatherarguments(ctx, name):
return ctx
def usectx(ctx):
print("Name is %s" % ctx.params['name'])
if __name__ == '__main__':
ctx = gatherarguments(standalone_mode=False)
print(ctx)
usectx(ctx)
Output:
./clickme.py --name something
<click.core.Context object at 0x7fb671a51690>
Name is something

Creating a shell command line application with Python and Click

I'm using click (http://click.pocoo.org/3/) to create a command line application, but I don't know how to create a shell for this application.
Suppose I'm writing a program called test and I have commands called subtest1 and subtest2
I was able to make it work from terminal like:
$ test subtest1
$ test subtest2
But what I was thinking about is a shell, so I could do:
$ test
>> subtest1
>> subtest2
Is this possible with click?
This is not impossible with click, but there's no built-in support for that either. The first you would have to do is making your group callback invokable without a subcommand by passing invoke_without_command=True into the group decorator (as described here). Then your group callback would have to implement a REPL. Python has the cmd framework for doing this in the standard library. Making the click subcommands available there involves overriding cmd.Cmd.default, like in the code snippet below. Getting all the details right, like help, should be doable in a few lines.
import click
import cmd
class REPL(cmd.Cmd):
def __init__(self, ctx):
cmd.Cmd.__init__(self)
self.ctx = ctx
def default(self, line):
subcommand = cli.commands.get(line)
if subcommand:
self.ctx.invoke(subcommand)
else:
return cmd.Cmd.default(self, line)
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
repl = REPL(ctx)
repl.cmdloop()
#cli.command()
def a():
"""The `a` command prints an 'a'."""
print "a"
#cli.command()
def b():
"""The `b` command prints a 'b'."""
print "b"
if __name__ == "__main__":
cli()
There is now a library called click_repl that does most of the work for you. Thought I'd share my efforts in getting this to work.
The one difficulty is that you have to make a specific command the repl command, but we can repurpose #fpbhb's approach to allow calling that command by default if another one isn't provided.
This is a fully working example that supports all click options, with command history, as well as being able to call commands directly without entering the REPL:
import click
import click_repl
import os
from prompt_toolkit.history import FileHistory
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
"""Pleasantries CLI"""
if ctx.invoked_subcommand is None:
ctx.invoke(repl)
#cli.command()
#click.option('--name', default='world')
def hello(name):
"""Say hello"""
click.echo('Hello, {}!'.format(name))
#cli.command()
#click.option('--name', default='moon')
def goodnight(name):
"""Say goodnight"""
click.echo('Goodnight, {}.'.format(name))
#cli.command()
def repl():
"""Start an interactive session"""
prompt_kwargs = {
'history': FileHistory(os.path.expanduser('~/.repl_history'))
}
click_repl.repl(click.get_current_context(), prompt_kwargs=prompt_kwargs)
if __name__ == '__main__':
cli(obj={})
Here's what it looks like to use the REPL:
$ python pleasantries.py
> hello
Hello, world!
> goodnight --name fpbhb
Goodnight, fpbhb.
And to use the command line subcommands directly:
$ python pleasntries.py goodnight
Goodnight, moon.
I know this is super old, but I've been working on fpbhb's solution to support options as well. I'm sure this could use some more work, but here is a basic example of how it could be done:
import click
import cmd
import sys
from click import BaseCommand, UsageError
class REPL(cmd.Cmd):
def __init__(self, ctx):
cmd.Cmd.__init__(self)
self.ctx = ctx
def default(self, line):
subcommand = line.split()[0]
args = line.split()[1:]
subcommand = cli.commands.get(subcommand)
if subcommand:
try:
subcommand.parse_args(self.ctx, args)
self.ctx.forward(subcommand)
except UsageError as e:
print(e.format_message())
else:
return cmd.Cmd.default(self, line)
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
repl = REPL(ctx)
repl.cmdloop()
#cli.command()
#click.option('--foo', required=True)
def a(foo):
print("a")
print(foo)
return 'banana'
#cli.command()
#click.option('--foo', required=True)
def b(foo):
print("b")
print(foo)
if __name__ == "__main__":
cli()
I was trying to do something similar to the OP, but with additional options / nested sub-sub-commands. The first answer using the builtin cmd module did not work in my case; maybe with some more fiddling.. But I did just run across click-shell. Haven't had a chance to test it extensively, but so far, it seems to work exactly as expected.

importing a class from another python file issue

Hi I want to import a class from another file in python, everything works however the class I want to import receives a command line argument. After I import (which is successful) how would I supply that command line argument in the class? (side:note is this a superclass or something? idk what that means)
#class I'm importing
class trend:
def __init__(self):
self.user_name = sys.argv[1] #receives commandline argument
_______________________________________________________________
#class I want to use it in
class game:
def __init__(self):
self.twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
def do_something(self):
import filenameforthetrendclass as TC
game = TC.trend() #how do I bring in the commandline argument in here?
It's a bad idea to use sys.argv anywhere but at the top-level (main, if you have one). That's something you should pass in.
First, re-write Trend to take user_name as a parameter to its constructor.
Quick example with just Trend:
class Trend(object):
def __init__(self, user_name):
self.user_name = user_name
trend = Trend(sys.argv[1])
Now integrating the whole concept:
import sys
from filenameforthetrendclass import Trend
class Game(object):
def __init__(self):
self.twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
def do_something(self, user_name):
a_trend = Trend(user_name) # was originally 'game = '
def main():
game = Game()
game.do_something(sys.argv[1])
Notes:
I'm using uppercase names for classes
I'm definitely not having a class named game and then using game as a local variable somewhere.

Categories

Resources