Skip to content

Commit

Permalink
add command line interface
Browse files Browse the repository at this point in the history
  • Loading branch information
pablovegan committed Jun 21, 2023
1 parent 3d530bd commit eb79ccd
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 33 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,14 +559,35 @@ The green tick near the commits shows that the workflows were successful.
*Cool tip: we can add a badge at the beginning of our readme to show that the tests either passed or failed (this is updated automatically each time the tests are run).*


### Command line interface

Command line interfaces (CLI) allow us to invoke functions or scripts through the command line without using `python -m` to execute a module. To do this, we need to create a script contain the CLI (for example, [`__main__.py`](mypackage/__main__.py)) and set it as the [entry point](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) in our [`pyproject.toml`](pyproject.toml) file
```toml
[project.scripts]
vector = "mypackage.__main__:main"
```

Python's Standard library provides a package to handle command line arguments to create this CLIs: `argparse`. You can follow along this [tutorial](https://realpython.com/command-line-interfaces-python-argparse) to learn how to use it.

As an example, we can use the command `vector` —which calls `main()` inside [`__main__.py`](mypackage/__main__.py)— to create and save a vector to a file:

```console
$ vector 1 2 --save vector.pkl
Vector (1.0, 2.0) created!
Vector pickled in data/vector.pkl!
```

## Other things to look into
- [List comprehensions](https://www.programiz.com/python-programming/list-comprehension). They generally replace lambda functions, `map()` and `filter()`.
- [Exception handling](https://www.programiz.com/python-programming/exception-handling): `try-except` statements. They work very well with custom error classes. An example can be found in the [`4-exceptions.ipynb`](examples/4-exceptions.ipynb) notebook inside the `examples` folder.
- [Iterators and generators](https://www.datacamp.com/tutorial/python-iterators-generators-tutorial): look up the functions `iter()` and `next()`, and the keyword `yield`. Generators are a central feature of Python and they are used in [coroutines](https://www.youtube.com/watch?v=rjBSOHXW5DY) and [context managers](https://realpython.com/python-with-statement/).
- [Function and class decorators](https://www.programiz.com/python-programming/decorator): decorators are a simple sintax to transform certain functions or classes. An example of an interesting decorator is [`@classmethod`](https://www.programiz.com/python-programming/methods/built-in/classmethod), which allows us, for example, to initialize classes in alternative ways without overly complicating the `__init__` method.
- [Dataclasses](https://realpython.com/python-data-classes/#basic-data-classes): a concise syntax for classes containing mainly data, similar to structures in C. The library [Pydantic](https://docs.pydantic.dev/latest/) implements automatic type checking when initializing a dataclass.
<<<<<<< HEAD
- [Pre-commits](https://pre-commit.com/): pre-commit hooks allow us to do certain actions before commiting changes with git. For example, we can lint our code with Ruff and fix it with Black whenever we make a commit.
- [Entry points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html): invoke functions or scripts from [command line](https://realpython.com/command-line-interfaces-python-argparse/) without using `python -m`.
=======
- [Pre-commits](https://pre-commit.com/): pre-commit hooks allow us to do certain actions before commiting changes with git. For example, we can lint our code with Ruff and fix it with Black whenever we make a commit.
>>>>>>> 31c6ad4 (add command line interface)
- [Logging](https://realpython.com/python-logging/): keep a record of what is happening in your program.


Expand Down
62 changes: 62 additions & 0 deletions mypackage/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Command line interface. This script will allow us to
execute commands of our library directly from the terminal
in a similar fashion as libraries like black or pytest.
References:
https://realpython.com/command-line-interfaces-python-argparse/
Example:
>>> vector 1 0
Vector (1.0, 0.0) created!
>>> vector 1 2 --save vector.pkl
Vector (1.0, 2.0) created!
Vector pickled in data/hey.pkl!
"""
from argparse import ArgumentParser

from .vector import Vector


def main():
parser = ArgumentParser( # this object parses the arguments given through command line
prog="vector", # name of our command
description="Create a 2D vector.",
epilog="Thanks for using %(prog)s! :)",
)
parser.add_argument(
"coordinates", # positional argument
nargs=2,
type=float,
metavar=("x", "y"),
help="take the Cartesian coordinates %(metavar)s",
)
parser.add_argument(
"-s", # optional argument
"--save", # flag can be given both in long or short fomat
action="store", # stores whatever goes after --save. This is the default value for action.
type=str,
)
args = parser.parse_args() # after parsing the arguments we can access them
x, y = args.coordinates[0], args.coordinates[1]
vector = Vector(x, y)

print(f"Vector {vector} created!")

if args.save:
# Usually imports are at the top, but sometimes it is superflous to import them all
import pickle
from pathlib import Path

Path("data/").mkdir(exist_ok=True)
file_path = Path("data") / args.save

with open(file_path, mode="wb") as f:
pickle.dump(vector, f)

print(f"Vector pickled in {file_path}!")


if __name__ == "__main__":
main()
71 changes: 39 additions & 32 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
######## Building

[build-system]
requires = [
"setuptools",
"setuptools-scm",
]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "python-tips-tools"
authors = [
{name = "Pablo V. Parellada", email = "pablo.veganzones@uva.es"}
]
authors = [{ name = "Pablo V. Parellada", email = "pablo.veganzones@uva.es" }]
maintainers = [
{name = "Pablo V. Parellada", email = "pablo.veganzones@uva.es"}
{ name = "Pablo V. Parellada", email = "pablo.veganzones@uva.es" },
]
description = "This is an example python project."
readme = "README.md"
license = {file = "LICENSE"}
license = { file = "LICENSE" }
requires-python = ">=3.10"
keywords = ["tutorial"]
classifiers = [
Expand All @@ -34,21 +29,9 @@ repository = "https://github.com/pablovegan/Python-tips-tools"
documentation = "https://pablovegan.github.io/Python-tips-tools/"

[project.optional-dependencies]
test = [
"pytest",
"pyinstrument"
]
lint = [
"ruff",
"black",
"black[jupyter]",
"mypy"
]
build = [
"setuptools",
"build",
"twine"
]
test = ["pytest", "pyinstrument"]
lint = ["ruff", "black", "black[jupyter]", "mypy"]
build = ["setuptools", "build", "twine"]
docs = [
"mkdocs",
"mkdocstrings",
Expand All @@ -61,22 +44,20 @@ docs = [
"mkdocs-section-index",
"mkdocs-same-dir",
"mkdocs-autorefs",
"mkdocs-jupyter"
"mkdocs-jupyter",
]
dev = ["python-tips-tools[test,lint,build,docs]"]

[project.scripts]
# add here command line interface scripts
# my-script = "mypackage.module:function"
vector = "mypackage.__main__:main"

[tool.setuptools]
platforms = ["unix", "linux", "osx", "cygwin", "win32"]
packages = ["mypackage"]

[tool.setuptools.dynamic]
version = {attr = "mypackage._version.__version__"}
dependencies = {file = ["requirements.txt"]}

version = { attr = "mypackage._version.__version__" }
dependencies = { file = ["requirements.txt"] }


######## Tools
Expand All @@ -89,10 +70,36 @@ testpaths = "tests"
[tool.ruff]
line-length = 100
select = ["F", "E"]
extend-select = ["W", "I002", "B", "UP", "PLE", "PLW", "NPY", "RUF", "PD", "SIM", "PT"]
extend-select = [
"W",
"I002",
"B",
"UP",
"PLE",
"PLW",
"NPY",
"RUF",
"PD",
"SIM",
"PT",
]
unfixable = ["NPY002"]
ignore = []
fixable = ["E", "F", "W", "I", "B", "UP", "PLE", "PLW", "NPY", "RUF", "PD", "SIM", "PT"]
fixable = [
"E",
"F",
"W",
"I",
"B",
"UP",
"PLE",
"PLW",
"NPY",
"RUF",
"PD",
"SIM",
"PT",
]
extend-exclude = ["tests", "test"]
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

Expand Down

0 comments on commit eb79ccd

Please sign in to comment.