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
Asked By: Sean

||

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'
Answered By: tzaman

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()   
Answered By: gerardw
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.