<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cuadros Nicolas</title>
    <description>The latest articles on DEV Community by Cuadros Nicolas (@cuacua).</description>
    <link>https://dev.to/cuacua</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F259869%2F60025dc9-0c15-4558-99ff-798ad2e48c8d.png</url>
      <title>DEV Community: Cuadros Nicolas</title>
      <link>https://dev.to/cuacua</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cuacua"/>
    <language>en</language>
    <item>
      <title>Build a small command line interface by using a Python function</title>
      <dc:creator>Cuadros Nicolas</dc:creator>
      <pubDate>Sat, 25 Jan 2020 20:53:43 +0000</pubDate>
      <link>https://dev.to/cuacua/build-a-small-command-line-utility-by-using-a-python-function-35aa</link>
      <guid>https://dev.to/cuacua/build-a-small-command-line-utility-by-using-a-python-function-35aa</guid>
      <description>&lt;p&gt;Hi everyone ! I'm a French student in computer science and today I will speak about a module I made to automate the generation of CLI using the inspect and argparse module of Python. It's my first post on DEV.to. For me It's a challenge on multiple points, on one hand it forced me to write in English (I try my best to avoid mistakes) and in the other It make me communicate about my works and (I hope) receive criticsm about it. Also I hope It will give you new ideas based on the inspect module. Now let's dive into the subject !&lt;/p&gt;

&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;It’s been a while since I use Python to build small scripts for Deep Learning algorithms and basically parsing some files. Most of the times these scripts end up with some parameters that I have to tweak before running them. In order to avoid to modify the script each time , I use the argparse module to build a small CLI from them. Unfortunately, in most cases, the script that I want to run with parameters is just one main function with some parameters. Here you can see a small example with the prototype of a function that transform a csv file to a json file and how you have to use the argparse module to build your CLI :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csvToJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;infile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"out.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Simple function allowing to build a json file based on a csv one
    :param infile: Name of the file to transform
    :param outfile: Name of the output file
    :param separator: Token use by the csv file to separate columns
    """&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;



&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Simple function allowing to build a json file based on a csv one"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"infile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Name of the file to transform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"--outfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Name of the output file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"out.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"--separator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Token use by the csv file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;csvToJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you try to get the help of the CLI you will end up with this :&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;usage: test.py &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt; OUTFILE] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt; SEPARATOR] infile

Simple &lt;span class="k"&gt;function &lt;/span&gt;allowing to build a json file based on a csv one

positional arguments:
  infile                Name of the file to transform

optional arguments:
  &lt;span class="nt"&gt;-h&lt;/span&gt;, &lt;span class="nt"&gt;--help&lt;/span&gt;            show this &lt;span class="nb"&gt;help &lt;/span&gt;message and &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; OUTFILE, &lt;span class="nt"&gt;--outfile&lt;/span&gt; OUTFILE
                        Name of the output file
  &lt;span class="nt"&gt;-s&lt;/span&gt; SEPARATOR, &lt;span class="nt"&gt;--separator&lt;/span&gt; SEPARATOR
                        Token use by the csv file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It's a pretty nice formatting of what we specified in our code, but as you can see, you have to declare a second time every parameter of your function, its argument names and types. I found it a little bit painful to do on a daily basis, so I got the idea to automate all this stuff, by retrieving information from the function prototype and build a command arg parser from this information.&lt;/p&gt;
&lt;h1&gt;
  
  
  The inspect module
&lt;/h1&gt;

&lt;p&gt;In order to automate this, the start point was the inspect module that contains a function named "getfullargspec". This function allows to find most of the information of your functions like :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of arguments and theirs names&lt;/li&gt;
&lt;li&gt;Their type annotations&lt;/li&gt;
&lt;li&gt;And their default value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example with the above example, this function give use these informations :&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;specs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getfullargspec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvToJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;#['infile', 'outfile', 'separator']
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;#('out.json', '\t')
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;#{'outfile': &amp;lt;class 'str'&amp;gt;, 'separator': &amp;lt;class 'str'&amp;gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So perfect ! We already have everything we need to automatically build our CLI! Except the name of the function, but you can retrieve it by using the .&lt;strong&gt;name&lt;/strong&gt; private attributes of the function. And so from this small function, I build a library that allows you to build your CLI from your function prototype.&lt;/p&gt;
&lt;h1&gt;
  
  
  The module
&lt;/h1&gt;

&lt;p&gt;The module is named autofunccli and you can get it via pip :&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;autofunccli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If we go back to our example, we can now simply use the library to build our CLI :&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;autofunccli&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cmdfunc&lt;/span&gt;

&lt;span class="c1"&gt;#The cmdfunc function can be use as a decorator.
#It will build a FunctionArgParser object.
&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cmdfunc&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csvToJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;infile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"out.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Simple function allowing to build a json file based on a csv one
    :param infile: Name of the file to transform
    :param outfile: Name of the output file
    :param separator: Token use by the csv file to separate columns
    """&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;

&lt;span class="c1"&gt;#You can now use the .main method with the __name__ variable to test if it's
#the main call and in this case, parse the command line arguments and launch the function
&lt;/span&gt;&lt;span class="n"&gt;csvToJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you try to get the help of the CLI you will end up with this :&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;usage: test.py &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt; OUTFILE] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt; SEPARATOR] infile

Simple &lt;span class="k"&gt;function &lt;/span&gt;allowing to build a json file based on a csv one

positional arguments:
  infile                Name of the file to transform : &amp;lt;str&amp;gt;

optional arguments:
  &lt;span class="nt"&gt;-h&lt;/span&gt;, &lt;span class="nt"&gt;--help&lt;/span&gt;            show this &lt;span class="nb"&gt;help &lt;/span&gt;message and &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; OUTFILE, &lt;span class="nt"&gt;--outfile&lt;/span&gt; OUTFILE
                        Name of the output file : &amp;lt;str&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nt"&gt;-s&lt;/span&gt; SEPARATOR, &lt;span class="nt"&gt;--separator&lt;/span&gt; SEPARATOR
                        : &amp;lt;str&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;out.json&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;RETURN]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You will end up with almost the same help as above but, with some differences like the type of each argument and their default values are printed. The code is way much easier to read and you don't have to copy paste everything you have already written in your function prototypes.&lt;br&gt;
This module uses the function annotations to know how to convert the strings, for instance if you specify an int in the annotations, the module will call the int function to parse the string.&lt;br&gt;
Of course the module has some limitations, for instance you can only use some default types of Python and you also don't have the same flexibility as the argparse module (like exclusive arguments).&lt;/p&gt;

&lt;p&gt;I haven't told everything about autofunccli, you can build a multi-function CLI, use variadic arguments or even enumerations ! So if you want to see more be sure to check out the repository :&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/CuadrosNicolas"&gt;
        CuadrosNicolas
      &lt;/a&gt; / &lt;a href="https://github.com/CuadrosNicolas/Python-Command-Function"&gt;
        Python-Command-Function
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This project aims to build an easy to use package allowing to use a python function directly as a command line script using the argparse module.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;I found this module really interesting to build and learned a lot about the inspect module and how you can make some meta-programming stuff in Python. There is a lot to say about this module and how you can automate some parts of Python and how you can conjugate this type of runtime analysis and the meta class of Python.&lt;/p&gt;

&lt;p&gt;If you want to know more about the inspect module you can check the &lt;a href="https://docs.python.org/3/library/inspect.html"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article and this module, if you think we can improve it, tell me in the comments !&lt;/p&gt;

</description>
      <category>python</category>
    </item>
  </channel>
</rss>
