Skip to content

Commit 335fc86

Browse files
committed
finish v1
1 parent f4d4bd1 commit 335fc86

File tree

6 files changed

+201
-128
lines changed

6 files changed

+201
-128
lines changed

README.md

Lines changed: 197 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,75 +33,236 @@ A CLI is a program that is run from the command-line and provides a text-based
3333
interface to allow you to carry out various tasks. While the functionality of
3434
CLIs is limited by the capabilities of Terminal (in Mac OS) and Command
3535
Shell/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/)

lib/grade_reports.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env python3
2+
3+
if __name__ == '__main__':
4+
pass

testing/__init__.py

Whitespace-only changes.

testing/class_test.py

Lines changed: 0 additions & 39 deletions
This file was deleted.

testing/conftest.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

testing/module_test.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)