__init__.py
, in terms of where you store all your logic. That was enough to get us started but we can do better than that.
Let’s add a file called saying.py. This file should go in the inner proverb
directory.
Take the logic that was in __init__.py
and move it to this new file, so it looks like this:
1 2 3 4 5 6 7 8 |
def saying(): return ( u'Let not mercy and truth forsake thee:\n' u'Bind them about thy neck;\n' u'write them upon the table of thine heart.\n' u'\n' u'Proverbs 3:3' ) |
__init__.py
file:
1 |
from .saying import saying |
(Re)Install It
If you wanted to install your package again to see if it works, you can try this:pip install .This is the same thing we did last time. However, you are likely to encounter a message saying something like: “Requirement already satisfied (use –upgrade to upgrade).” What this is saying is that some package called “proverb” at version “0.1” (assuming you used the same version I did) is already in place. It’s basically telling you what to do:
pip install . --upgradeYou could also, of course, change the version and, in fact, that is what you are likely to do when you make changes like we did. Logically, we haven’t changed how our package works so it would run the same way as before:
>>> import proverb >>> print(proverb.saying())
Add a Dependency
Let’s add another package that our package will depend on. If you’re writing a test solution, you will likely be relying on other packages. So let’s see how this is done. The common contrivance in these kind of simple tutorials is to use a formatting language. So let’s use Markdown. Specifically, our package will depend on the markdown package. We have to add this dependency into oursetup.py
file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
setup( name='proverb', version='0.1', description='Statement to Live By', long_description=readme(), classifiers=[ 'Development Status :: 3 - Alpha', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', 'Topic :: Text Processing :: Linguistic', ], keywords='bible proverbs', url='http://github.com/testerstories/proverb', author='Jeff Nyman', author_email='jeff@gmail.com', license='MIT', packages=['proverb'], install_requires=['markdown'], include_package_data=True, zip_safe=False ) |
saying.py
file to import this package and then use it. Change the code in that file as such:
1 2 3 4 5 6 7 8 9 10 |
from markdown import markdown def saying(): return markdown ( u'Let not _mercy_ and _truth_ forsake thee:\n' u'Bind them about thy neck;\n' u'write them upon the table of thine heart.\n' u'\n' u'**Proverbs 3:3**' ) |
setup.py or requirements.txt?
There can be confusion regarding these two files. What’s important to know is that you’ll have what are called “abstract dependencies” in your setup.py file and you’ll have “concrete dependencies” in your requirements.txt file. The idea is that a dependency is “abstract” when it’s stated only as a name, possibly with an optional version specifier attached. A dependency is “concrete” when it also specifies where the dependency should be gathered from or what specific version should be used. The main thing to really understand is that apip install
command, which someone will likely use to install your package, does not look at the requirements.txt file by default; instead it only looks at an “install_requires” section in setup.py. However, where you will see requirements.txt files become handy is if someone is using your package in a virtual environment or if they want to contribute to developing your application.
Some people like to have both files, however, and I personally think this makes sense. The nice thing is that you can maintain your dependencies in requirements.txt and then have setup.py use that file. You can also just have pip use the file, like this:
pip install -r requirements.txtBut, again, why? Why have two files? The core problem is that pip has no dependency resolver. So pip, at the time I write this, uses the first specification (and thus version) that it finds for a project package. Requirements files are used to force pip to properly resolve dependencies. I won’t go into that too much here. For now, just be aware of this.
Command Line Scripts
If you provide test solutions, you might provide a script that allows someone to use your solution as a test runner. This means your users need a way to run that script from the command line. There is one approach where you create abin
directory, put a Python script file in there, and then use a “scripts” keyword in your setup.py
file. I’m not going to do that. Instead I’m going to take another approach, which involves using what’s called an entry point.
In this case, the entry point is referred to as “console_scripts”. This allows Python functions to be directly registered as command-line accessible tools.
Note that it’s a Python function that is registered as a command line statement to be executed. If you instead wanted, say, a bash shell script, then you would have to go the “scripts” route with a “bin” directory.
In your inner proverb
directory, create a file called command_line.py and put the following in it:
1 2 3 4 |
import proverb def main(): print(proverb.saying()) |
>>> import proverb.command_line >>> proverb.command_line.main()Now the main() function can then be registered in setup.py. Make the following change to that file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
setup( name='proverb', version='0.1', description='Statement to Live By', long_description=readme(), classifiers=[ 'Development Status :: 3 - Alpha', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', 'Topic :: Text Processing :: Linguistic', ], keywords='bible proverbs', url='http://github.com/testerstories/proverb', author='Jeff Nyman', author_email='jeff@gmail.com', license='MIT', packages=['proverb'], install_requires=['markdown'], entry_points = { 'console_scripts': ['proverb-saying=proverb.command_line:main'], }, include_package_data=True, zip_safe=False ) |
Testing
I most definitely am not going to go into every nuance of unit testing your solutions with Python; at least not in this series. But I do want to include at least one test just to show you how to do it. These tests should generally be placed in a submodule of your project. The reason for this is that this approach means your tests can be imported, but they won’t pollute the global namespace. So here’s an example of where you can create atests
directory:
proverb tests __init__.py proverb __init__.py command_line.py saying.py setup.pyNotice you have an __init__.py to mark the directory as a module. In this case, that file can be empty. Now create a file called test_saying.py and put the following in it:
1 2 3 4 5 6 7 8 |
from unittest import TestCase import proverb class TestSaying(TestCase): def test_is_string(self): s = proverb.saying() self.assertTrue(isinstance(s, str)) |
pip install noseNow you can run it from the directory of your project:
nosetestsYou should see that the single test passes. That’s great but let’s make sure we can run our tests via our setup.py as well as make Nose a dependency. Modify setup.py as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
setup( name='proverb', version='0.1', description='Statement to Live By', long_description=readme(), classifiers=[ 'Development Status :: 3 - Alpha', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', 'Topic :: Text Processing :: Linguistic', ], keywords='bible proverbs', url='http://github.com/testerstories/proverb', author='Jeff Nyman', author_email='jeff@gmail.com', license='MIT', packages=['proverb'], install_requires=['markdown'], tests_require=['nose'], test_suite='nose.collector', entry_points = { 'console_scripts': ['proverb-saying=proverb.command_line:main'], }, include_package_data=True, zip_safe=False ) |
python setup.py testAnd that should cover us for now. In this post, we’ve updated our project to use a dependency, to provide a command-line script, and to incorporate some tests. This is important stuff when you are creating a test solution in Python. So here I’ve shown you the simplest way to get started with these and (hopefully) gain some confidence. In the next, and last, part of this series I’ll cover packaging up your solution and deploying it. Join me for the finale in part 3.