0

I have python 3.10 project that uses a combination of scraping websites, data analysis, and additional APIs. Some utility modules may be used by the scraping and data analysis modules. I'm fundamentally misunderstanding something about how imports work in Python. For example, in sl_networking.py, I try to import the Result class from result.py:

from ...util.result import Result

Producing the error:

PS C:\Development\TradeAssist> & c:/Development/TradeAssist/.venv/Scripts/python.exe c:/Development/TradeAssist/libs/scrapers/sl/sl_networking.py
Traceback (most recent call last):
  File "c:\Development\TradeAssist\libs\scrapers\sl\sl_networking.py", line 1, in <module>
    from ...util.result import Result
ImportError: attempted relative import with no known parent package

The project structure I'm currently using is:

TradeAssist
|__libs
|  |__broker_apis
|  |  |__ibapi
|  |__data_analysis
|  |  |__sl
|  |  |__ta
|  |__scrapers
|  |  |__sl
|  |  |  |  sl_auth.py
|  |  |  |__sl_networking.py
|  |  |__ta
|  |__util
|     |__result.py
|__tests
   |__test_sl.py
   |__test_ta.py

If I have a common utility function that I expect to use within the data_analysis and scraper modules, how should I be structuring my project and handling imports?

GoldenJoe
  • 7,874
  • 7
  • 53
  • 92
  • How, and from which directory, are you running your code? Executing `python -m libs.scrapers.sl.sl_networking` from `TradeAssist` directory should not generate this error. (Also, relative imports are commonly used between files with closely related functionality; for this, you might want to use absolute module path, as three levels up is not that obviously related and is less readable — I had to count off levels while head-parsing your command) – Amadan Mar 28 '22 at 03:46
  • I’m not trying to run sl_networking.py at all. I want to run scripts in the tests folder. – GoldenJoe Mar 28 '22 at 11:47
  • It doesn't matter. I am saying that that would have worked, if that was what you were doing - so I know that is not what you were doing. The question remains: how are you running whatever scripts you are running, and from which directory? Without that answer, it is not really possible to help you, except to retell you what the docs already explain about how imports work. – Amadan Mar 28 '22 at 18:03
  • Straight from VSCode, I assume it just executes the script directly. – GoldenJoe Mar 28 '22 at 19:45

2 Answers2

2

This is not a Python question, but rather a VSCode question. The way VSCode runs Python files by default is a little bit dumb. When you click the little triangle to "Run Python File" on test_sl, VSCode will run a command such as this:

/usr/local/bin/python3 /path/to/TradeAssist/tests/test_ta.py

Python by default initialises sys.path to contain the directory containing the file being run, in addition to its own libraries, and whatever is in your PYTHONPATH variable. This means your sys.path looks something like this (with ... being Python's libraries)

['/path/to/TradeAssist/tests', ...]

However, this means code in lib is not reachable; only files inside tests will be correctly found by Python. I.e. out of the box, VSCode is only able to run Python files that are in your root source folder (e.g. src, or directly in the root workspace folder).

There are several solutions.

The first one is simplest: decide that your TradeAssist is the source root, and put any files you want to execute directly there. They will be able to import any files under them, and the files under them will be able to use both relative and absolute imports correctly. This has obvious disadvantages — you would be self-limiting yourself to one directory.

The second is to set up VSCode to tell Python where your source root(s) are, by defining PYTHONPATH. This is fairly complex.

The third is the simplest, and likely the correct case here: use VSCode's testing functionality. Configure the testing framework (presumably using unittest and test_*.py test file pattern), then just run the tests. If you consider TradeAssist to be your source root, it will work correctly (i.e. import libs.scrapers.sl.sl_networking will work correctly inside your tests, and from ...util.result import Result will work correctly inside your sl_networking.py). The testing function will take care of running the tests for you in a correct fashion.

However, if you want to consider libs to be your source root (i.e. you would like to do import scrapers.sl.sl_networking), or if you want to be able to run arbitrary files, not just tests, then you have to fall back to method #2: messing with PYTHONPATH.

tl;dr: Don't run test files manually, let VSCode do it for you by setting up tests correctly.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • This answer led me to finding the correct VS Code configuration settings seen in this answer: https://stackoverflow.com/a/62581540/1183071 – GoldenJoe Apr 02 '22 at 05:40
0

Relative imports only work when the code is executed from the outermost parent root. In the current scenario, you can only execute the code at or above libs directory.

python -m scrapers.sl.sl_networking

should work fine if you are running this at libs directory.

Once the project is structured, it is easy to run the individual scripts from the top parent directory using -m flag, as no refactoring will be required. If the code has to be executed from the script parent directory, the following has to be done:

  1. Use absolute imports instead of relative imports.
  2. Add the directory to the path python searches for imports. This can be done in several ways. Add it to the PYTHONPATH env variable or use any of the sys.path.append or sys.path.insert hacks, which can be easily found.
Lemon Reddy
  • 553
  • 3
  • 5
  • Maybe my intent wasn’t clear? I’m trying to run scripts in the tests module. Everything under libs is support functions/classes. I was running the script test_sl.py directly in VS Code. I had read other SO questions in which absolute paths were discouraged. I was attempting to separate executable and non-executable scripts. What is the real world workflow supposed to be? – GoldenJoe Mar 28 '22 at 11:52