Using Python Click within a class
Question:
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
Answers:
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()
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()