Skip to content

Use Click to parse command-line arguments in lemke.py#10

Open
nataliemes wants to merge 7 commits into
gambitproject:mainfrom
nataliemes:refactor/click-cli
Open

Use Click to parse command-line arguments in lemke.py#10
nataliemes wants to merge 7 commits into
gambitproject:mainfrom
nataliemes:refactor/click-cli

Conversation

@nataliemes
Copy link
Copy Markdown
Collaborator

@nataliemes nataliemes commented May 23, 2026

This PR replaces manual command-line argument parsing with Click (in lemke.py).

Changes in detail

  • Instead of using global variables, main() accepts arguments (populated by Click), which are then passed onto runlemke().
  • If lcpfilename argument was not a valid file path, the program used to raise an unhandled exception. Now, this argument is checked by Click and if the file does not exist, or is not readable, a clean CLI error is shown automatically.
  • Options follow POSIX convention: single-character flags use a single dash (-v) and can be combined (-vs), while multi-character flags use double dashes (--verbose) to avoid ambiguity.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the lemke CLI in src/lemke/lemke.py from manual sys.argv parsing to Click, aiming to improve help/usage output and validate the input file path up front.

Changes:

  • Replaced manual argument parsing with a Click-based main() command (options + lcpfilename argument).
  • Updated tableau.runlemke() to accept lcpfilename and derive the .out filename from it.
  • Added click as a runtime dependency in pyproject.toml.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/lemke/lemke.py Introduces Click CLI decorators/options/argument parsing and threads lcpfilename into runlemke() for .out generation.
pyproject.toml Adds Click as a project dependency to support the new CLI.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lemke/lemke.py Outdated
"lcpfilename",
default="lcp",
required=False,
type=click.Path(exists=True, readable=True),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nataliemes good suggestion

Comment thread src/lemke/lemke.py Outdated
Comment on lines 457 to 461
outfile = lcpfilename + ".out"

if silent:
filehandle = open(outfile, "w") # noqa: SIM115
n = self.n
Comment thread src/lemke/lemke.py Outdated
Comment on lines +545 to +551
printout(f"verbose={verbose} lcpfilename={lcpfilename} silent={silent} z0={z0}")
# printout (f"{verbose}= {lcpfilename}= {silent}= {z0}=")
m = lcp(lcpfilename)
printout(m)
printout("==================================")
tabl = tableau(m)
tabl.runlemke(verbose=verbose, z0=z0, silent=silent)
tabl.runlemke(verbose=verbose, z0=z0, silent=silent, lcpfilename=lcpfilename)
Comment thread pyproject.toml
Comment thread src/lemke/lemke.py Outdated
def main():
processArguments()
@click.command(
context_settings={"help_option_names": ["-?", "--help"]},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add "-h" as another help option name

Comment thread src/lemke/lemke.py Outdated
outfile = lcpfilename + ".out"

if silent:
filehandle = open(outfile, "w") # noqa: SIM115
Copy link
Copy Markdown
Member

@stengel stengel May 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think when silent==True there should not be any output at all (except for error messages),
including no output to file. This will also avoid the open-file handle leak risk noted by copilot.
The standard output to file is recording the pivots, if verbose==True also the intermediate tableaus.
We have to look at lemke.py as well as check for further parameters, later.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, when silent is True, there should be no output at all (neither to a file, nor to the console). But when silent is False, should we print to both, or only to a file?

Copy link
Copy Markdown
Member

@stengel stengel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks very good. See my suggestions, e.g. from copilot - reject directories,
and no output if silent==True.

nataliemes and others added 3 commits May 24, 2026 14:42
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@tturocy
Copy link
Copy Markdown
Member

tturocy commented May 26, 2026

Some guerrilla comments on instrumenting the command-line tool, based on how command-line tools typically work:

  • Don't allow a default for the input filename - it should be a required argument.
  • You might consider allowing "-" as an input filename, in which case the data are read from standard input, useful for piping
  • I would suggest NEVER suppress output to stdout. If someone really wants to suppress that then you pipe to /dev/null.
  • If you allow a logfile to be generated, the logfile might be more detailed than the stdout output.
  • Think about all of the things you might include in/include out of a detailed logfile, and have command-line switches (or a list of arguments) for these.

Specifically for Python:

  • Try very hard to use context managers for opening files. Certainly do NOT use global variables, never never ever!

Looking ahead, a logical refactoring step is going to be to have callback functions to the main lemke routine, which will be the mechanism where you'll actually write to output if desired - you won't have any printing going on in lemke proper. But that is a few steps down the road - but the above will move in the direction of making that easy.

@stengel
Copy link
Copy Markdown
Member

stengel commented May 27, 2026

Don't allow a default for the input filename - it should be a required argument.

  • Fine, use perhaps [filename].lcp and [filename].log - see below.

You might consider allowing "-" as an input filename

  • No

I would suggest NEVER suppress output to stdout. If someone really wants to suppress that then you pipe to /dev/null.

  • when lemke is used as a library, we don't want to write to stdout. We write to stderr if the log file cannot be opened (presumably due to missing permissions).
    Any progress reports during the computation should also be written, if requested, to stderr.
    Question is what kind of stdout result we use when using lemke standalone from the CLI.
    I think we have to iterate here, rather than aiming for the perfect solution now.

If you allow a logfile to be generated, the logfile might be more detailed than the stdout output.

  • maybe we should indeed call it [filename].log and the main file [filename].lcp

Think about all of the things you might include in/include out of a detailed logfile

  • we will do exactly that when we look at the algorithm. The current CLI arguments are just a start.

Try very hard to use context managers for opening files

  • That's using "with" as in
with open("file.txt", "r") as f:
      content = f.read()

and the question is how to use that together with click which is in effect the main program calling lemke when using the CLI. There must be a standard solution for this.

@tturocy
Copy link
Copy Markdown
Member

tturocy commented May 27, 2026

When this is used as a library we will have gotten to the point where all of the writes are done by callbacks, as I noted in my previous comment, so lemke will not know anything about whether a file is being written, a data structure is being built, user interaction is happening, whatever.

Being able to read from standard input can be very useful so you don't have to create temporary files.

Filename suffixes shouldn't be assumed, they should be specified explicitly on the command line - leave it to the user how they want to do it.

Using a context manager is straightforward, wrap the call to runlemke in it:

with open(output_filename, "w") as f:
   runlemke(<arguments, which would include f as the output stream>)

Eventually we will just pass callback(s) to runlemke and hide the stream entirely, and then there are various ways to encapsulating the file opening to ensure RAII....

@nataliemes
Copy link
Copy Markdown
Collaborator Author

Here are the recent changes I made (although not sure if this is what you had in mind):

  • Added an upper bound for Click dependency
  • Added -h as a help option name
  • Made lcpfilename argument reject directories and removed its default value
  • For output:
    Previously everything was printed to console, and if we set silent to True, then the output was sent to a file instead. As I understood, silent option worked like this, so that when runlemke(silent=True) was called in code, it wouldn't output everything to console and run quietly while writing to a file (like it was done in bimatrix.py).
    In the current version, runlemke() accepts an output_stream argument instead of silent. You pass stdout for console output, a file handle for file output, or None (default) to suppress all output – this last option is used in tests and also in bimatrix.py (but if we want to output to a file like before, we can do that as well).
    The CLI entry point passes stdout to runlemke(). I didn't add a Click option that would work like silent and send all output to a file instead, since I think the user could do that simply by: lemke lcp > lcp.out.

@nataliemes nataliemes requested a review from stengel May 28, 2026 19:42
@tturocy tturocy self-requested a review May 29, 2026 11:29
Comment thread src/lemke/lemke.py
class tableau:
# filling the tableau from the LCP instance Mqd
def __init__(self, Mqd):
self.output_stream = None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing the stream as state in the tableau is not great. However, this is more of a symptom of a deeper problem than a problem in itself - the tableau class mixes confuddles up the tableau object with the lemke algorithm, which are logically distinct. Further refactorings will solve this, so I think this is OK for now because the current commit is about getting click to be responsible for the command-line parsing.

Comment thread pyproject.toml
version = "0.0.1"
dependencies = [
"numpy>=2.2,<2.3",
"matplotlib>=3.10,<3.11"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upper bounds on packages should only be used in the event of known incompatibilities, especially when working with mature packages (like numpy, matplotlib, click all are). Upper bounds can cause version conflicts when installing alongside other packages in the same environment.

@nataliemes nataliemes changed the title Use Click to parse command-line arguments Use Click to parse command-line arguments in lemke.py Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants