IP Challenge: Part 2

Last entry we learned how to set up our work environment, a minimal project structure, we installed the required packages and also we learned how to make our tests pass.

In this chapter we’ll learn how to create and import a class from a different module in different folders, also how to initialize a class into an object and manipulate our object’s representation.


Challenge 2: Class Creation and Imports

Unit Test

Test Trigger:
pipenv run python -m unittest tests/challenge

Challenge Instructions

  • Open the file ipinfo.py where you will create a class called IPInfo, you can write an empty class for this challenge.
  • After you create the class, go to __init__.py import the Class itself (not the module containing the class).
  • Define a __all__ to include your class in the __init__.py

Walkthrough

This challenge requires that we create a class called IPInfo inside our module ipinfo.py which is in the modules directory. You don’t have to create a class with methods and initializing values just yet, it is only asking for a class which can be empty right now.

Let’s open ipinfo.py and create a class

# modules/ipinfo.py
class IPInfo():
pass

Now that we have defined our class let’s import it and initialize it in our module, let’s open __init__.py

# modules/__init__.py
from .ipinfo import IPInfo

__all__ = ['IPInfo']

Now we re-run our test and everything should be O.K

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

What Happened Here?

The __init__.py is necessary to treat our modules directory as a package, this means that we can import it in our python programs. Usually when you import a python file you can do so by typing:

>>> import mymodule

To import a python file inside another folder you can do so by typing:

>>> import myfolder.mymodule

Now, if we create an empty file called __init__.py inside myfolder then python will treat it as a package instead of a regular folder and you can import all the modules that the package myfolder contains:

>>> import myfolder

Or we can import only one module if we wanted to:

>>> from myfolder import mymodule

This is what is happening to our package modules that we initialized as a package with the __init__.py file:

>>> import modules
>>> modules
<module 'modules' from '/home/franccesco/workspace/ipchallenge/modules/__init__.py'>
>>> modules.ipinfo
<module 'modules.ipinfo' from '/home/franccesco/workspace/ipchallenge/modules/ipinfo.py'>
>>> modules.ipinfo.IPInfo
<class 'modules.ipinfo.IPInfo'>

The initialization file also let us do some other stuff, for example, I don’t want to use my module like modules.ipinfo.IPInfo(), I would like to write only IPInfo() instead, we can import our IPInfo class directly into our initialization file, let’s open it.

# modules/__init__.py
from .ipinfo import IPInfo

What about that . before ipinfo? We are saying that we want to import the module from the current folder instead of a module or package loaded in our PYTHONPATH, you can leave the dot behind if you want, but for the sake of being explicit we will leave it there.

Now we can import our class directly without having to write the package name and the module name:

>>> from modules import IPInfo

But there’s one thing, if we run flake8 to check if our code is PEP8 compliant it will find an issue with our initialization file:

$ pipenv run flake8 modules
modules/__init__.py:1:1: F401 'ipinfo.IPInfo' imported but unused

It complains that we’re not using our class IPInfo, here’s were __all__ can help us out. We define __all__ to explicitly define a list of the modules that we are importing, this will fix the flake8 problem.

That is enough for our first challenge, we learned how to import our modules correctly, from other folders and the current folders, also we learned how to respect the PEP8 in our initialization file. Let’s see what the next challenge will bring to our table.