You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: source/exercises/mailroom/mailroom-pkg.rst
+48-31Lines changed: 48 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,29 +10,25 @@ Code Structure
10
10
11
11
Start with your existing version of mailroom.
12
12
13
-
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.
13
+
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.
14
14
15
15
The first step is to re-structure your code into separate files:
16
16
17
-
- one (or more) for the logic code: the code than manipulates the data
18
-
- one for the user-interface code: the code with the interactive loops and all the "input" and "print" statements
19
-
- one (or more) for tests.
17
+
- One (or more) for the logic code: the code that manipulates the data
18
+
- One for the user-interface code: the code with the interactive loops and all the "input" and "print" statements
19
+
- One (or more) for the unit tests.
20
20
21
21
You should have all this pretty distinct after having refactored for the unit testing. If not, this is a good time to do it!
22
22
23
-
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:
23
+
In addition to those three, you will need a single function to call that will start the program.
24
+
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.
25
+
That file can then have an ``if __name__ == "__main__"`` block
26
+
which should be as simple as:
24
27
25
28
.. code-block:: python
26
29
27
-
#!/usr/bin/env python
28
-
from mailman import cli
29
-
30
30
if__name__=="__main__":
31
-
cli.main()
32
-
33
-
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.
34
-
35
-
.. 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.
31
+
main()
36
32
37
33
38
34
Making the Package
@@ -51,27 +47,53 @@ Put all these in a python package structure, something like this::
51
47
test_model.py
52
48
test_cli.py
53
49
54
-
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)
50
+
You will need to import the logic code from model.py in the cli code in order to use it.
51
+
You can wait until you learn about mocking to write the code in test_cli.py (so you can leave that out)
55
52
56
-
Now write your ``setup.py`` to support your package.
53
+
Now write a ``setup.py`` file to support the installation of your package.
57
54
58
55
59
56
Making the top-level script runnable
60
57
------------------------------------
61
58
62
59
To get the script installed you have two options. I used to prefer the more straightforward one, `the scripts keyword argument <http://python-packaging.readthedocs.io/en/latest/command-line-scripts.html#the-scripts-keyword-argument>`_
63
60
64
-
But it turns out that while the simple ``scripts`` keyword argument method works well and is simple, it may not work as well on Windows -- it relies in your script being named ``something.py`` and that Windows is configured to run all files with ``.py`` extensions. Not all windows systems are set up this way. But the "entry points" method builds a little exe file to call your script, so it's more reliable.
61
+
But it turns out that while the simple ``scripts`` keyword argument method works well and is simple, it may not work as well on Windows -- it relies in your script being named ``something.py`` and that Windows is configured to run all files with ``.py`` extensions. Not all windows systems are set up this way. But the "entry points" method builds a little ``.exe`` file to call your script, so it's more reliable.
65
62
66
-
And the Python community has moved very much towards using setuptools entry points, so That's really the way to go now:
63
+
And the Python community has moved very much towards using setuptools entry points, so that's really the way to go now:
That's a bit complicated, so I'll break it down for you. In this case, we only want a single script, but setuptools allows multiple types of entry points, and multiple instances of each type (e.g. you can have more than one script) to the code, so the argument is a potentially large dictionary.
74
+
The keys of the dict are the various types of entry points.
75
+
In this case, we want a single script that can be run in a terminal (or "console"), so we have a dict with one key: ``console_scripts``.
76
+
77
+
The value of that entry is a list of strings -- each one describing the console script. This string is of the form::
78
+
79
+
SCRIPTNAME=MODULE:FUNCTION_NAME
80
+
81
+
setuptools will create a wrapper script with the name given, and that wrapper will call the function in the module that is specified.
82
+
So: ``'mailroom=mailroom.cli:main'`` means: create a start up script called "mailroom" that will then call the ``main`` function in the ``cli`` module in the ``mailroom`` package.
83
+
84
+
Once this is all set up, and you install the package (either in editable mode or not)::
85
+
86
+
pip install -e ./
87
+
88
+
you should then be able to type "mailroom" at the command line and have your program run.
89
+
70
90
71
-
Including data files
91
+
Including Data Files
72
92
--------------------
73
93
74
-
NOTE: If you have a database of donors in a file that you load, then that should go in the package as well. Probably inside the mailroom dir, in a ``data`` dir or similar. Then you need to add it to your setup.py to make sure it gets copied into the installed package.
94
+
If you have a database of donors in a file that you load, then that should go in the package as well. Probably inside the mailroom dir, in a ``data`` dir or similar. Then you need to add it to your setup.py to make sure it gets copied into the installed package.
95
+
96
+
(If you are not saving the donor data to a file -- that's fine. You can ignore this section for now)
75
97
76
98
There are a few ways to do this:
77
99
@@ -83,7 +105,10 @@ I personally like the simplest one with the least magic:
83
105
84
106
Then you'll get the data file included in the package in the same place.
85
107
86
-
Now you'll need to write your code to find that data file. You can do that by using the __file__ module attribute -- then the location of the data file will be relative to the __file__ that your code is in. A little massaging with a ``pathlib.Path`` should do it.
108
+
Now you'll need to write your code to find that data file.
109
+
You can do that by using the ``__file__`` module attribute, which is the path to a python module at run time -- then the location of the data file will be relative to the path that your code is in.
110
+
A little massaging with a ``pathlib.Path`` should do it.
111
+
87
112
88
113
Testing your Package
89
114
--------------------
@@ -104,23 +129,15 @@ When that is done, you should be able to run the top-level script from anywhere:
104
129
105
130
.. code-block:: bash
106
131
107
-
$ mailroom.py
132
+
$ mailroom
108
133
109
134
and run the test from within the package:
110
135
111
136
.. code-block:: bash
112
137
113
138
$ pytest --pyargs mailroom
114
139
115
-
If you installed in editable mode, then you can update the code and re-run the tests or the script, and it will use the new code right away.
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
140
+
(or run the tests from the test dir as well)
125
141
142
+
If you installed in editable mode, then you can update the code and re-run the tests or the script, and it will use the new code right away.
0 commit comments