diff --git a/README.md b/README.md index 124083e..417bd7a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Python210CourseMaterials +# Python310CourseMaterials -Course Materials for Python 210 +Course Materials for Python 310: the first course in the UWPCE Python Certificate Program. Published here: diff --git a/source/class_schedule/index.rst b/source/class_schedule/index.rst index 27cf4f0..764b8b2 100644 --- a/source/class_schedule/index.rst +++ b/source/class_schedule/index.rst @@ -1,7 +1,7 @@ :orphan: ######################### -Python 210 Class Schedule +Python 310 Class Schedule ######################### .. toctree:: diff --git a/source/exercises/html_renderer/html_renderer_tutorial.rst b/source/exercises/html_renderer/html_renderer_tutorial.rst index 762688c..6539107 100644 --- a/source/exercises/html_renderer/html_renderer_tutorial.rst +++ b/source/exercises/html_renderer/html_renderer_tutorial.rst @@ -110,13 +110,13 @@ But this one failed: assert file_contents.startswith("") assert file_contents.endswith("") -OK -- this one really does something real -- it tries to render an html element -- which did NOT pass -- so it's time to put some real functionality in the Element class. +OK -- this one really does something real -- it tries to render an html element -- which did NOT pass -- so it's time to put some real functionality in the ``Element`` class. This is the code: .. code-block:: python - class Element(object): + class Element: def __init__(self, content=None): pass @@ -139,7 +139,7 @@ So we need to add a tiny bit of code: .. code-block:: python - class Element(object): + class Element: tag = "html" @@ -261,9 +261,9 @@ So the method looks something like this: .. code-block:: python def render(self, out_file): + out_file.write("<{}>\n".format(self.tag)) # loop through the list of contents: for content in self.contents: - out_file.write("<{}>\n".format(self.tag)) out_file.write(content) out_file.write("\n") out_file.write("{}>\n".format(self.tag)) @@ -492,7 +492,8 @@ Part A .. rubric:: Instructions: -"Create a couple subclasses of ``Element``, for each of ````, ``
``, and ```` tags. All you should have to do is override the ``tag`` class attribute (you may need to add a ``tag`` class attribute to the ``Element`` class first, if you haven't already)." +"Create a couple subclasses of ``Element``, for each of ````, ``
``, and ```` tags. +All you should have to do is override the ``tag`` class attribute (you may need to add a ``tag`` class attribute to the ``Element`` class first, if you haven't already)." So this is very straightforward. We have a class that represents an element, and the only difference between basic elements is that they have a different tag. For example:: @@ -509,7 +510,9 @@ and::
-The ```` tag is for the entire contents of an html page, and the ```` tag is for a paragraph. But you can see that the form of the tags is identical, so we don't have to change much to make classes for these tags. In fact, all we need to change is the ``tag`` class attribute. +The ``
`` tag is for the entire contents of an html page, and the ```` tag is for a paragraph. +But you can see that the form of the tags is identical, so we don't have to change much to make classes for these tags. +In fact, all we need to change is the ``tag`` class attribute. Before we do that -- let's do some test-driven development. Uncomment the next few tests in ``test_html_render.py``: ``test_html``, ``test_body``, and ``test_p``, and run the tests:: @@ -546,7 +549,8 @@ Before we do that -- let's do some test-driven development. Uncomment the next f ====================== 3 failed, 4 passed in 0.08 seconds ====================== So we have three failures. Of course we do, because we haven't written any new code yet! -Yes, this is pedantic, and there is no real reason to run tests you know are going to fail. But there is a reason to *write* tests that you know are going to fail, and you have to run them to know that you have written them correctly. +Yes, this is pedantic, and there is no real reason to run tests you know are going to fail. +But there is a reason to *write* tests that you know are going to fail, and you have to run them to know that you have written them correctly. Now we can write the code for those three new element types. Try to do that yourself first, before you read on. @@ -664,24 +668,28 @@ Uncomment ``test_subelement`` in the test file, and run the tests:: out_file = <_io.StringIO object at 0x10325b5e8> def render(self, out_file): + out_file.write("<{}>\n".format(self.tag)) # loop through the list of contents: for content in self.contents: - out_file.write("<{}>\n".format(self.tag)) > out_file.write(content) E TypeError: string argument expected, got 'P' html_render.py:26: TypeError ====================== 1 failed, 7 passed in 0.11 seconds ====================== -Again, the new test failed; no surprise because we haven't written any new code yet. But do read the report carefully; it did not fail on an assert, but rather with a ``TypeError``. The code itself raised an exception before it could produce results to test. +Again, the new test failed; no surprise because we haven't written any new code yet. +But do read the error report carefully; it did not fail on an assert, but rather with a ``TypeError``. The code itself raised an exception before it could produce results to test. So now it's time to write the code. Look at where the exception was raised: line 26 in my code, inside the ``render()`` method. The line number will likely be different in your code, but it probably failed on the render method. Looking closer at the error:: > out_file.write(content) E TypeError: string argument expected, got 'P' -It occurred in the file ``write`` method, complaining that it expected to be writing a string to the file, but it got a ``'P'``. ``'P'`` is the name of the paragraph element class. -So we need a way to write an element to a file. How might we do that? Inside the element's render method, we need to render an element... +It occurred in the file ``write`` method, complaining that it expected to be writing a string to the file, but it got a ``'P'``. +``'P'`` is the name of the paragraph element class. +So we need a way to write an element to a file. How might we do that? + +Inside the element's render method, we need to render an element... Well, elements already know how to render themselves. This is what is meant by a recursive approach. In the ``render`` method, we want to make use of the ``render`` method itself. @@ -696,25 +704,25 @@ it becomes clear -- we render an element by passing the output file to the eleme .. code-block:: python def render(self, out_file): + out_file.write("<{}>\n".format(self.tag)) # loop through the list of contents: for content in self.contents: - out_file.write("<{}>\n".format(self.tag)) out_file.write(content) out_file.write("\n") - out_file.write("{}>\n".format(self.tag)) + out_file.write("{}>\n".format(self.tag)) So let's update our render by replacing that ``out_file.write()`` call with a call to the content's ``render`` method: .. code-block:: python def render(self, out_file): + out_file.write("<{}>\n".format(self.tag)) # loop through the list of contents: for content in self.contents: - out_file.write("<{}>\n".format(self.tag)) # out_file.write(content) content.render(out_file) out_file.write("\n") - out_file.write("{}>\n".format(self.tag)) + out_file.write("{}>\n".format(self.tag)) And let's see what happens when we run the tests:: @@ -779,20 +787,21 @@ There are a number of approaches you can take. This is a good time to read the n You may want to try one of the more complex methods, but for now, we're going to use the one that suggests itself from the error. We need to know whether we want to call a ``render()`` method, or simply write the content to the file. How would we know which to do? Again, look at the error: + We tried to call the render() method of a piece of content, but got an ``AttributeError``. So the way to know whether we can call a render method is to try to call it -- if it works, great! If not, we can catch the exception, and do something else. In this case, the something else is to try to write the content directly to the file: .. code-block:: python def render(self, out_file): + out_file.write("<{}>\n".format(self.tag)) # loop through the list of contents: for content in self.contents: - out_file.write("<{}>\n".format(self.tag)) try: content.render(out_file) except AttributeError: out_file.write(content) out_file.write("\n") - out_file.write("{}>\n".format(self.tag)) + out_file.write("{}>\n".format(self.tag)) And run the tests again:: @@ -815,7 +824,7 @@ So what are the downsides to this method? Well, there are two: 1. When we successfully call the ``render`` method, we have no idea if it's actually done the right thing -- it could do anything. If someone puts some completely unrelated object in the content list that happens to have a ``render`` method, this is not going to work. But what are the odds of that? -2. This is the bigger one: if the object *HAS* a render method, but that render method has something wrong with it, then it could conceivably raise an ``AttributeError`` itself, but it would not be the Attribute Error we are expecting. The trick here is that this is very hard to debug. +2. This is the bigger one: if the object *HAS* a render method, but that render method has something wrong with it, then it could conceivably raise an ``AttributeError`` itself, but it would not be the ``AttributeError`` we are expecting. The trick here is that this can be very hard to debug. However, we are saved by tests. If the render method works in all the other tests, It's not going to raise an ``AttributeError`` only in this case. Another reason to have a good test suite. @@ -845,7 +854,7 @@ OK, that should have been straightforward. Now this part: Some html elements don't tend to have a lot of content, such as the document title. So it makes sense to render them all on one line. This is going to require a new render method. Since there are multiple types of elements that should be rendered on one line, we want to create a base class for all one-line elements. It should subclass from ``Element``, and override the render method with a new one, which will be pretty much the same as the main ``Element`` method, but without the newlines. -Before we do that though -- let's write a test for that! Because the ``ONeLineTag`` class is a base class for actual elements that should be rendered on one line, we really don't need to write a test directly for it. We can write one for its first subclass: ``Title``. The title elements should be rendered something like this:: +Before we do that though -- let's write a test for that! Because the ``OneLineTag`` class is a base class for actual elements that should be rendered on one line, we really don't need to write a test directly for it. We can write one for its first subclass: ``Title``. The title elements should be rendered something like this::
\n") out_file.write("".join(open_tag)) @@ -1205,7 +1213,7 @@ Note that I added a space after the ``p`` in the test. Now my test is failing on A paragraph of text
-However, my code for rendering the opening tag is a bit klunky -- how about yours? Perhaps you'd like to refactor it? Before you do that, you might want to make your tests a bit more robust. This is really tricky. It's very hard to test for everytihng that might go wrong, without nailing down the expected results too much. For example, in this case, we haven't tested that there is a space between the two attributes. In fact, this would pass our test:: +However, my code for rendering the opening tag is a bit klunky -- how about yours? Perhaps you'd like to refactor it? Before you do that, you might want to make your tests a bit more robust. This is really tricky. It's very hard to test for everything that might go wrong, without nailing down the expected results too much. For example, in this case, we haven't tested that there is a space between the two attributes. In fact, this would pass our test::
A paragraph of text
@@ -1315,7 +1323,7 @@ You'll need to override the ``render()`` method:
What needs to be there? Well, self closing tags can have attributes, same as other elements.
So we need a lot of the same code here as with the other ``render()`` methods. You could copy and paste the ``Element.render()`` method, and edit it a bit. But that's a "Bad Idea" -- remember DRY (Don't Repeat Yourself)?
You really don't want two copies of that attribute rendering code you worked so hard on.
-How do we avoid that? We take advantage of the power of subclassing. If you put the code to render the opening (and closing) tags in it's own method, then we can call that method from multiple render methods, something like:
+How do we avoid that? We take advantage of the power of subclassing. If you put the code to render the opening (and closing) tags in its own method, then we can call that method from multiple render methods, something like:
.. code-block:: python
diff --git a/source/exercises/list_lab.rst b/source/exercises/list_lab.rst
index f4c7860..b366e56 100644
--- a/source/exercises/list_lab.rst
+++ b/source/exercises/list_lab.rst
@@ -29,9 +29,9 @@ to query the user for info at the command line, you use:
Procedure
---------
-In your student dir in the class repo, create a ``lesson03`` dir and put in a new ``list_lab.py`` file.
+In the github classroom repo for this exercise, you will find a``list_lab.py`` file (if it not there, you can create it and add it to git yourself).
-The file should be an executable Python script. That is to say that one
+The file should be made an executable Python script. That is to say that one
should be able to run the script directly like so:
.. code-block:: bash
@@ -50,11 +50,11 @@ should be able to run the script directly like so:
The file will also need this on the first line::
- #!/usr/bin/env python3
+ #!/usr/bin/env python
-This is known as the "she-bang" line -- it tells the shell how to execute that file -- in this case, with ``python3``
+This is known as the "she-bang" line -- it tells the shell how to execute that file -- in this case, with ``python``
-NOTE: on Windows, there is a python launcher which, if everything is configured correctly will look at that line to know you want python3 if there is more than one python on your system.
+NOTE: on Windows, there is a python launcher which, if everything is configured correctly will look at that line to know you want python if there is more than one python on your system.
If this doesn't work on Windows, just run the file some other way:
@@ -63,14 +63,11 @@ If this doesn't work on Windows, just run the file some other way:
- from your IDE or editor is you are using one
-Add the file to your clone of the repository and commit changes frequently
+Make sure the file is added to your clone of the repository and commit changes frequently
while working on the following tasks. When you are done, push your changes to
-GitHub and issue a pull request.
+GitHub and issue a pull request to let the instructors know it is ready for review.
-(if you are still struggling with git -- just write the code for now).
-
-When the script is run, it should accomplish the following four series of
-actions:
+When the script is run, it should accomplish the following four series of actions:
Series 1
--------
@@ -79,8 +76,8 @@ Series 1
- Display the list (plain old ``print()`` is fine...).
- Ask the user for another fruit and add it to the end of the list.
- Display the list.
-- Ask the user for a number and display the number back to the user and the
- fruit corresponding to that number (on a 1-is-first basis). Remember that Python uses zero-based indexing, so you will need to correct.
+- Ask the user for a number and display the number back to the user
+ and the fruit corresponding to that number (on a 1-is-first basis). Remember that Python uses zero-based indexing, so you will need to correct for that.
- Add another fruit to the beginning of the list using "+" and display the
list.
- Add another fruit to the beginning of the list using ``insert()`` and display the list.
@@ -105,8 +102,8 @@ Again, using the list from series 1:
- Ask the user for input displaying a line like "Do you like apples?" for each fruit in the list (making the fruit all lowercase).
- For each "no", delete that fruit from the list.
-- For any answer that is not "yes" or "no", prompt the user to answer with one
- of those two values (a while loop is good here)
+- For any answer that is not "yes" or "no", prompt the user to answer
+ with one of those two values (a while loop is good here)
- Display the list.
Series 4
diff --git a/source/exercises/mailroom/mailroom-oo.rst b/source/exercises/mailroom/mailroom-oo.rst
index 0a84e9b..5484002 100644
--- a/source/exercises/mailroom/mailroom-oo.rst
+++ b/source/exercises/mailroom/mailroom-oo.rst
@@ -8,9 +8,7 @@ Making Mailroom Object Oriented.
**Goal:** Refactor the mailroom program using classes to help organize the code.
-The functionality is the same as the earlier mailroom:
-
-:ref:`exercise_mailroom_part1`
+The functionality is the same as the earlier mailroom(s).
But this time, we want to use an OO approach to better structure the code to make it more extensible.
@@ -32,6 +30,9 @@ As you design appropriate classes, keep in mind these three guidelines for good
There should be no use of the ``input()`` function in the classes that hold the data! Nor any use of ``print()`` -- these are for user interaction, and you want the data handling code to be potentially usable with totally different user interaction -- such as a desktop GUI or Web interface.
+In the spirit of separation of concerns, you also want keep your Command Line Interface code in a separate file(s), as they already should be in your mailroom-as-a-package version. In fact, you should be able to keep the structure of your package pretty much the same -- simply replacing the model code with classes.
+
+
3) As always: **DRY** (Don't Repeat Yourself): Anywhere you see repeated code; refactor it!
@@ -52,7 +53,14 @@ For this simple problem, simple tuples could work fine. However, in order for th
So now you have to think about using a dict or class. Again for flexibility, a dict is a bit easier; you can add fields to it very easily. However, with a class, you can build some functionality in there, too. This is where encapsulation comes in. For instance, one thing you might want to do is get the total of all donations a donor has made in the past. If you add a method to compute that (or a property!), then the rest of the code doesn't need to know how the donations are stored.
-Consider ``data[0]`` vs ``data["name"]`` vs ``data.name``. Which one is more readable? Keep in mind that another benefit of using OO for data encapsulation is ability of modern IDEs to provide auto-completion, which reduces the number of bugs and helps to produce code faster.
+Consider ``data[0]`` (stored in a tuple) vs ``data["name"]`` (stored in a dict) vs ``data.name`` (a class).
+Which one is more readable?
+Keep in mind that another benefit of using OO for data encapsulation is ability of modern IDEs to provide auto-completion, which reduces the number of bugs and helps to produce code faster.
+
+Another way to think about it is "data" vs "code" -- dicts are for data, classes are for code.
+Sometimes it's not totally clear which is which, but if you think about how static is, that can be be a guide.
+In this example, every donor is going to have a name, and a donation history, and maybe in the future a bunch of other information (address, email, phone number ....).
+Whatever it is, every donor will have the same set -- so it's good to use code to manage it.
Below are more detailed suggestions on breaking down your existing code into multiple modules that will be part of a single mailroom program.
@@ -60,26 +68,32 @@ Below are more detailed suggestions on breaking down your existing code into mul
Modules and Classes
...................
-You may organize your code to your preference and keep it simple by having all of the code in a single file.
+You may organize your code to your preference and keep it simple by having all of the model code in a single file. But you should at least separate the code that manipulates data (the "model" code) from the command line interface code, as you did for mailroom-as-a-package.
-Optionally, you could organize your code into modules, which helps to keep code organized and re-usable.
+Optionally, you could further organize your model code into separate modules: maybe one for the Donor object, one for the DonorCollection object.
What is a module? A module is a python file with a collection of code that can be imported into other python files.
Modules can contain functions, classes, and even variables (constants).
-Here is an example file structure for ``mailroom_oo`` package that contains 3 modules:
+Here is an example file structure for ``mailroom_oo`` package -- it should be similar to what you already have:
-.. code-block:: bash
+::
- └── mailroom_oo
- ├── cli_main.py
- ├── donor_models.py
- └── test_mailroom_oo.py
+ └── mailroom
+ ├── __init__.py
+ ├── cli.py
+ ├── model.py
+ └── tests
+ ├── __init__.py
+ ├── test_cli.py
+ └── test_model.py
-The module ``donor_models.py`` can contain the ``Donor`` and ``DonorCollection`` classes.
-The module ``cli_main.py`` would include all of your user interaction functions and main program flow.
+The module ``model.py`` can contain the ``Donor`` and ``DonorCollection`` classes.
+
+The module ``cli.py`` would include all of your user interaction functions and main program flow (and a ``main()`` function that can be called to start up the program (and used as an entry_point).
+
``Donor`` Class
...............
@@ -117,7 +131,9 @@ This design allows you to quickly look up donor by their name and get a donor ob
Another option is to simply use a list of donor objects. You get to choose which you think is more appropriate.
-Remember that you should use `self.donors` attribute any time you want to work with data about a single donor, most of your methods in this class will utilize it in some way. This is really what classes are desined for.
+Remember that you should use `self.donors` attribute in any methods that need access the individual donors. Most of your methods in this class will utilize it in some way. This is really what classes are designed for.
+
+Note that external code probably shouldn't access the ``.donors`` list (or dict, or ...) directly but rather ask the DonorCollection class for the information it needs: e.g. if you need to add a new donation to a particular donor, calla method like ``find_donor()`` to get the donor you want, and then work with that Donor object directly.
**Examples:**
@@ -125,17 +141,15 @@ Generating a thank you letter to a donor only requires knowledge of that one don
Generating a report about all the donors requires knowledge of all the donors, so that code belongs in the ``DonorCollection`` class.
-Hint:
-You've previously sorted simple data structures like list and dictionaries, but here we're dealing with objects - not to worry that is a really simple thing to do with python!
-You can use `operator.attrgetter` with a sorted function (review python docs for usage documentation).
+.. note:: You've previously sorted simple data structures like list and dictionaries, but here we're dealing with objects -- not to worry, that is a really simple thing to do with python! You can use ``operator.attrgetter`` with the ``sorted`` function (review the python docs for usage documentation) to make it easy to sort based on various attributes of a Donor.
+
Command Line Interface
.......................
**Module responsible for main program flow (CLI - Command Line Interface)**
-Let's call this module ``cli_main.py`` to represent the entry point for the mailroom program.
-This module will be using the classes we defined: ``Donor`` and ``DonorCollection``.
+Let's call this module ``cli.py``. This module will be using the classes we defined: ``Donor`` and ``DonorCollection``.
It will also handle interaction with the user via the ``input`` function calls that gather user input and to provide the output to the console.
What should go into this module?
@@ -152,6 +166,10 @@ The idea here is that we should be able to fairly easy replace this CLI program
such as a GUI (Graphical User Interface), without having to make any changes to our data classes.
If that was the case, then you would implement the GUI elements and use your data classes the same way as they are used in CLI.
+Note that this arrangement is a one-way street: the CLI code will need to know about the Donor and DonorCollection classes, but the model code shouldn't need to know anything about the CLI code: it manages the information, and returns what's asked for (like a donor letter or report), but it doesn't know what is done with that information.
+
+In short: the cli.py module will import the model module, but the model module shouldn't import the cli module.
+
Test-Driven Development
-----------------------
@@ -170,15 +188,15 @@ That is, rather than take a non-OO function and try to make it a method of a cla
You should expect to re-use a lot of the command line interface code, while refactoring most of the logic code.
-If you are not sure at the start what functionality you data classes will need, you can start with the CLI code, and as you find the need for a function, add it to your data classes (after writing a test first, of course).
+If you are not sure at the start what functionality you data classes will need, you can start with the CLI code, and as you find the need for a function, add it to your model classes (after writing a test first, of course).
Exercise Guidelines
===================
-OO mailroom is the final project for the class.
+OO mailroom is the final step in the mailroom project. And you are near the end of the class.
-So this is your chance to really do things "right". Strive to make this code as good, by every definition, as you can.
+So this is your chance to really do things "right". Strive to make this code as good, by every definition, as you can. If you have gotten feedback from your instructors, now is the chance to incorporate recommended changes.
With that in mind:
@@ -209,9 +227,9 @@ Functionality
- You should be able to re-use all the logic code with a different UI -- Web App, GUI, etc.
- - There should be no ``input()`` or ``print`` functions in the logic code.
+ - There should be no ``input()`` or ``print()`` functions in the logic code.
- - The logic code should be 100% testable (without mocking input() or any fancy stuff like that)
+ - The logic code should be 100% testable (without mocking ``input()`` or any fancy stuff like that)
.. rubric:: Testing
@@ -231,9 +249,8 @@ Functionality
.. rubric:: The "soft" stuff:
Style:
- - conform to PEP8! (or another consistent style)
-
- - You can use 95 or some other reasonable number for line length
+ - Conform to PEP8! (or another consistent style)
+ (You can use 95 char or some other reasonable number for line length)
Docstrings:
Functions and classes should all have good docstrings. They can be very short if the function does something simple.
@@ -241,13 +258,16 @@ Docstrings:
Naming:
All classes, functions, methods, attributes, variables should have appropriate names: meaningful, but not too detailed.
+
Extra Ideas:
------------
In case you are bored -- what features can you add?
-* How about an html report using your html_render code?
-
* Fancier reporting
-* The sky's the limit
+* More error checking -- does the user really want to create a new donor? or was it a typo in the donor name?
+
+* The next project is an html renderer -- maybe use it to make a nice html report?
+
+* The sky's the limit!
diff --git a/source/exercises/mailroom/mailroom-pkg.rst b/source/exercises/mailroom/mailroom-pkg.rst
index 28fe14d..faa8a62 100644
--- a/source/exercises/mailroom/mailroom-pkg.rst
+++ b/source/exercises/mailroom/mailroom-pkg.rst
@@ -10,29 +10,25 @@ Code Structure
Start with your existing version of mailroom.
-It should already be structured with the "logic" code distinct from the user interface (yes, a command line *is* a user interface). But you may have it all in one file. This isn't *too* bad for such a small program, but as a program grows, you really want to keep things separate, in a well organized package.
+It may already be structured with the "logic" code distinct from the user interface (yes, a command line *is* a user interface). But you may have it all in one file. This isn't *too* bad for such a small program, but as a program grows, you really want to keep things separate, in a well organized package.
The first step is to re-structure your code into separate files:
- - one (or more) for the logic code: the code than manipulates the data
- - one for the user-interface code: the code with the interactive loops and all the "input" and "print" statements
- - one (or more) for tests.
+ - One (or more) for the logic code: the code that manipulates the data
+ - One for the user-interface code: the code with the interactive loops and all the "input" and "print" statements
+ - One (or more) for the unit tests.
You should have all this pretty distinct after having refactored for the unit testing. If not, this is a good time to do it!
-In addition to those three, you will want to write a top-level script file (perhaps called ``mailman.py``) that does little but import the ui code and run a ``main()`` function. It should look something like this:
+In addition to those three, you will need a single function to call that will start the program.
+That can be defined in a new file, as a "script", but for something as simple as this, it can be in with your interface code.
+That file can then have an ``if __name__ == "__main__"`` block
+which should be as simple as:
.. code-block:: python
- #!/usr/bin/env python
- from mailman import cli
-
if __name__ == "__main__":
- cli.main()
-
-Yes, that's it! This has the advantage of keeping the top-level script really simple, as it has to get put somewhere else and it can keep the "real" code in the package where it belongs.
-
-.. note:: Be careful here -- it is important not to call your top-level script the same thing as your package, in this case ``mailroom.py``. If you do, than when installed, python will find the script, rather than the package, when you do ``import mailroom``. You can call it ``mailroom`` without the Python, but that may confuse Windows.
+ main()
Making the Package
@@ -51,9 +47,10 @@ Put all these in a python package structure, something like this::
test_model.py
test_cli.py
-You will need to import the logic code from model.py in the cli code in order to use it. You can wait until you learn about mocking to write the code in test_cli.py (so you can leave that out)
+You will need to import the logic code from model.py in the cli code in order to use it.
+You can wait until you learn about mocking to write the code in test_cli.py (so you can leave that out)
-Now write your ``setup.py`` to support your package.
+Now write a ``setup.py`` file to support the installation of your package.
Making the top-level script runnable
@@ -61,17 +58,42 @@ Making the top-level script runnable
To get the script installed you have two options. I used to prefer the more straightforward one, `the scripts keyword argument