bweber13
bweber13

Reputation: 370

How does one handle multiple modules/packages in python?

I've spent hours researching this problem, and I'm still baffled. Please find my ignorance charming.

I'm building a python program that will allow me to pit two AIs against each other in a game of battleship.

Here's my directory structure:

.
├── ais_play_battleship
│   ├── game.py
│   ├── __init__.py
│   ├── player.py
│   └── ship.py
├── LICENSE
├── README.md
└── tests
    └── ship_test.py

2 directories, 7 files

Currently, I'm trying to write ship_test.py, but I cannot seem to import ais_play_battleship.ship. I get the dreaded "ModuleNotFoundError"

Here's what my research has taught me about my problem:

Here are my specific questions:

Please forgive me, as I'm not very good at asking questions on StackOverflow. Please tell me how I can improve.

Upvotes: 10

Views: 9587

Answers (3)

frndmg
frndmg

Reputation: 81

Run tests/ship_test.py as a module

python -m tests.ship_test

Upvotes: 1

bweber13
bweber13

Reputation: 370

I am answering my own question, as I haven't yet received a satisfactory answer. The best resource I've found is available here. In summary:

Python does NOT search the directory you run python from for modules. Furthermore, adding an __init__.py file to make a directory a package is not enough to make it visible to an instance of python running in another folder. You must also install that package. Therefore, the only two ways to access a module in another directory are:

  1. Install the packaged module in site-packages (this requires the creation of a setup.py file and installation using pip install . More information is available here.
  2. Modify path to resolve the module

I ended up settling with the second option, for reasons discussed below.

The first option requires one to reinstall the package at every change to a package. This is difficult on a constantly-changing codebase, but can be made easier by using build automation. However, I'd like to avoid this added complexity.

I shied away from the second option for a long time, because it seemed that modifying the path would require hard-coding the absolute path to my module, which is obviously unacceptable, as every developer would have to edit that path to fit their environment. However, this guide provides a solution to this problem. Create a ./tests/context.py file with the following contents:

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

Then, in my ship_test.py module, I imported the context and the module I needed:

import context
import ais_play_battleship.ship
# (I include the submodule ship because ais_play_battleship itself does not have
# any attributes or methods, and the submodule ship is the only one I am testing
# in ship_test.py)

This fits my project better, because it works as expected without having to worry about installing my package (or the method by which my package was installed).

Upvotes: 11

Thtu
Thtu

Reputation: 2032

To solve this problem without relying on hacking about your sys.path, create a setup.py file and as a build step for your test runner, have it run pip install . first. You might want to use a tool like tox.

In the top level directory:

setup.py

from setuptools import setup

setup(name='ais_play_battleship')

tox.ini

[tox]
envlist = py36, py37

[testenv]
deps=pytest
commands=
    pip install . --quiet
py.test -q

then run your tests (in this example we use tox to do this so that we can also configure how the test environment can be configured) : tox

Upvotes: 2

Related Questions