@@ -33,75 +33,236 @@ A CLI is a program that is run from the command-line and provides a text-based
3333interface to allow you to carry out various tasks. While the functionality of
3434CLIs is limited by the capabilities of Terminal (in Mac OS) and Command
3535Shell/Powershell (in Windows), _ everything you have learned in Phase 3 can be
36- carries out through a CLI._
36+ carried out through a CLI._
3737
3838***
3939
40- ## Lesson Section
40+ ## Building a Simple CLI
4141
42- Lorem ipsum dolor sit amet. Ut velit fugit et porro voluptas quia sequi quo
43- libero autem qui similique placeat eum velit autem aut repellendus quia. Et
44- Quis magni ut fugit obcaecati in expedita fugiat est iste rerum qui ipsam
45- ducimus et quaerat maxime sit eaque minus. Est molestias voluptatem et nostrum
46- recusandae qui incidunt Quis 33 ipsum perferendis sed similique architecto.
42+ Before we dive into best practices for Python CLIs, let's build a very simple
43+ CLI. Fork this lesson from GitHub and open up ` lib/grade_reports.py ` to follow
44+ along.
45+
46+ The first thing that we need to do is scaffold our CLI so that it can be run
47+ from the command line. To do this, we'll need to create a script. There are two
48+ pieces we know are going into the script regardless of the particulars: the
49+ ** shebang** and our ` if __name__ = '__main__' ` block:
4750
4851``` py
49- # python code block
50- print (" statement" )
51- # => statement
52+ # !/usr/bin/env python3
53+
54+ if __name__ == ' __main__' :
55+ pass
5256```
5357
54- ``` js
55- // javascript code block
56- console .log (" use these for comparisons between languages." )
57- // => use these for comparisons between languages.
58+ Remember that the shebang tells the command line that this program should be
59+ executed using the Python 3 interpreter. We can technically still run this as a
60+ script without it, but that would require us to write
61+ ` python lib/grade_reports.py ` every time we wanted to do so. Since other
62+ programmers using your CLI might not know about this requirement, you should
63+ always include the shebang. Run ` chmod +x lib/grade_reports.py ` to make your
64+ script executable.
65+
66+ The ` if __name__ == '__main__' ` block tells the interpreter that this script
67+ should only be run if ` lib/grade_reports.py ` itself is being called from the
68+ command line. This is important if you want to import any objects from
69+ ` lib/grade_reports.py ` into other modules- if you don't include this code block,
70+ the full script will be run whenever your other module runs the import. That's
71+ not likely to be a helpful feature in your CLI.
72+
73+ Let's start to add some very simple functionality to our CLI. We want to
74+ produce a grade report a full class of students; we're going to use Python's
75+ ` input() ` and ` open() ` functions to do so.
76+
77+ ``` py
78+ # !/usr/bin/env python3
79+
80+ def create_grade_report (student_grades ):
81+ with open (' reports/grade_report.txt' , ' w' ) as gr:
82+ gr.write(student_grades)
83+
84+ if __name__ == ' __main__' :
85+ student_grades = input (" Student name, grade: " )
86+ create_grade_report(student_grades)
5887```
5988
89+ Run ` lib/grade_reports.py ` to execute your script:
90+
6091``` console
61- echo "bash/zshell statement"
62- # => bash/zshell statement
92+ Student name, grade: Ben, F
93+ ```
94+
95+ Check the ` reports ` directory- you should see that ` grade_report.txt ` has
96+ been generated and contains your input! Still, this is not really useful for
97+ generating grade reports. Let's use a ` while ` loop to continue collecting
98+ student grades until there are no more to enter and a ` for ` loop to write the
99+ grades line-by-line:
100+
101+ ``` py
102+ # !/usr/bin/env python3
103+
104+ def create_grade_report (student_grades ):
105+ with open (' reports/grade_report.txt' , ' w' ) as gr:
106+ for grade in student_grades:
107+ # add '\n' to write grades on separate lines
108+ gr.write(grade + ' \n ' )
109+
110+ if __name__ == ' __main__' :
111+ student_grades = []
112+
113+ grade = input (" Student name, grade: " )
114+ while grade:
115+ student_grades.append(grade)
116+ # end when no grade is entered
117+ grade = input (" Student name, grade: " )
118+
119+ create_grade_report(student_grades)
63120```
64121
65122<details >
66123 <summary >
67- <em>Check for understanding text goes here! <code>Code statements go here.</code></em>
124+ <em>Which mode would we set <code>open()</code> to if we wanted to update
125+ <code>grade_report.txt</code>, rather than overwrite it?</em>
68126 </summary >
69127
70- <h3 >Answer.</h3 >
71- <p >Elaboration on answer.</p >
128+ <h3 ><code >'a'</code > for "append".</h3 >
72129</details >
73130<br />
74131
132+ Now let's enter a classroom's worth of grades:
133+
134+ ``` console
135+ Student name, grade: Ben, F
136+ Student name, grade: Prabhdip, A
137+ Student name, grade: Alvee, A
138+ Student name, grade: Jeff, B
139+ Student name, grade: Jerrica, A
140+ Student name, grade: Gustave, F
141+ Student name, grade: Katie, A
142+ Student name, grade: # hit enter to complete
143+ ```
144+
145+ Check the ` reports ` directory again- nowyou should see that ` grade_report.txt `
146+ has been regenerated and contains grades for all of your students!
147+
148+ There are still, of course, many ways to improve this CLI. The instructions
149+ for the end user could certainly be much clearer. Maybe you want to be able to
150+ toggle between updating and generating new grade reports-
151+ [ ` argparse ` ] ( https://docs.python.org/3/library/argparse.html ) would be
152+ helpful for that. Maybe you want timestamps for your grade reports to help you
153+ keep them organized- [ ` datetime ` ] ( https://docs.python.org/3/library/datetime.html )
154+ would be very helpful there. Maybe you're (rightfully) biased against ` .txt `
155+ files and want to keep your data in a spreadsheet-
156+ [ ` csv ` ] ( https://docs.python.org/3/library/csv.html ) would be the right module
157+ to import there.
158+
75159***
76160
77- ## Instructions
161+ ## Best Practices in CLI Design
162+
163+ Building a simple CLI doesn't take up too much code, but when you start to
164+ include a wide range of functionality in a single CLI, it can start to get
165+ messy. To make your life easier, keep a few things in mind as you work on
166+ your Phase 3 project:
78167
79- This is a ** test-driven lab** . Run ` pipenv install ` to create your virtual
80- environment and ` pipenv shell ` to enter the virtual environment. Then run
81- ` pytest -x ` to run your tests. Use these instructions and ` pytest ` 's error
82- messages to complete your work in the ` lib/ ` folder.
168+ ### Separate User Input from Functionality
83169
84- Instructions begin here:
170+ A CLI depends on user input and a lot of code to act on it, but that doesn't
171+ mean it all has to be in the same place. As with any other Python program,
172+ your CLI should be grouped into classes and functions. Your scripted code- that
173+ is, the code inside of your ` if __name__ == '__main__' ` block- should _ only
174+ include user input and calls to classes and functions._
85175
86- - Make sure to specify any class, method, variable, module, package names
87- that ` pytest ` will check for.
88- - Any other instructions go here.
176+ Let's take a look:
89177
90- Once all of your tests are passing, commit and push your work using ` git ` to
91- submit.
178+ ``` py
179+ # !/usr/bin/env python3
180+
181+ class MyClass :
182+ def __init__ (self , user_input )
183+ self .value = user_input
184+
185+ def my_function (my_object ):
186+ # returns a final value for the CLI workflow
187+
188+ if __name__ == ' __main__' :
189+ user_input = input (" Enter something here: " )
190+ my_object = MyClass(user_input)
191+ print (my_function(my_object))
192+ ```
193+
194+ Here, we've factored most of our code into a class ` MyClass ` and a function
195+ ` my_function() ` . The scripted portion of the CLI only takes user input, calls
196+ ` MyClass ` and ` my_function() ` , and outputs the final value of the workflow. You
197+ may find it necessary at times to include ` for ` loops, ` while ` loops, and
198+ ` if/elif/else ` statements in this portion of the CLI, but make sure to separate
199+ and organize your code into classes and functions whenever possible.
200+
201+ ### Validate User Input
202+
203+ You may have noticed in our ` grade_reports ` CLI that we did _ not_ validate user
204+ input. If you wanted to enter a number or a nonsensical string, not only could
205+ you have done so, but it would have been written to ` grade_report.txt ` !
206+
207+ A good CLI will check the format of user input before using it to perform any
208+ actions. This can be carried out using regular expressions through the [ ` re `
209+ module] ( https://docs.python.org/3/library/re.html ) , the built-in ` type() ` and
210+ ` isinstance() ` functions, the various Python operators, and more.
211+
212+ <details >
213+ <summary >
214+ <em>What two arguments does <code>isinstance()</code> take?</em>
215+ </summary >
216+
217+ <h3 >An object and a class.</h3 >
218+ <p ><code >isinstance()</code > will check to see if an object is an instance of
219+ a specific class.</p>
220+ </details >
221+ <br />
222+
223+ ### Keep the End User Informed
224+
225+ You may have noticed while using Pipenv that many things are printed to the
226+ command line while external libraries are added to your virtual environment.
227+ This lets you know that Pipenv is doing its job. Most importantly, Pipenv
228+ finishes with a message as to what was installed, where it was installed, and
229+ how to access it later on.
230+
231+ Compare this with our ` grade_reports ` CLI. What do we see when the script
232+ finishes execution? We can find our new file in the ` reports ` directory, but
233+ how would someone who downloaded your CLI from PyPI know that?
234+
235+ A CLI should always inform the end user of what it is doing and where to find
236+ output. This should always happen through the CLI itself, but it's often
237+ helpful to store the messages from a session in a ` logs ` directory as well.
238+
239+ ### Use External Libraries to Standardize Your Code
240+
241+ While we at Flatiron encourage you to show us as much of your amazing
242+ hand-written code as possible, it's often best to use popular external libraries
243+ to handle common programming tasks. This makes it easier to "onboard" new
244+ developers to your application if you ever need to hire a team to maintain it.
245+
246+ Two popular libraries that will help you make amazing CLIs are
247+ [ ` Click ` ] ( https://click.palletsprojects.com/en/8.1.x/ ) and
248+ [ ` Fire ` ] ( https://google.github.io/python-fire/guide/ ) . While we won't require
249+ you to use these in your Phase 3 project, we strongly recommend that you use
250+ one. (It's another library to add to your resumé!)
92251
93252***
94253
95254## Conclusion
96255
97- Conclusion summary paragraph. Include common misconceptions and what students
98- will be able to do moving forward.
256+ CLIs are a helpful tool for managing the data on your computer and on remote
257+ servers. A well-built CLI will allow you to let non-technical users complete
258+ many of the same tasks with your application that you can from the Python shell
259+ without very much training at all. Consider the best practices for building CLIs
260+ as you work through the Phase 3 project this week- make sure to look back at
261+ the earlier modules, too!
99262
100263***
101264
102265## Resources
103266
104- - [ Resource 1] ( https://www.python.org/doc/essays/blurb/ )
105- - [ Reused Resource] [ reused resource ]
106-
107- [ reused resource ] : https://docs.python.org/3/
267+ - [ Click documentation] ( https://click.palletsprojects.com/en/8.1.x/ )
268+ - [ The Python Fire Guide] ( https://google.github.io/python-fire/guide/ )
0 commit comments