3

Objectives

I'm trying to configure a project to make it work during development and after packaging. I need to achieve several objectives and I don't seem to be able to deal with them separately, making it right for one will fail some others.

  1. being able to run tests from command line, possibly after setting up environment variable PYTHONPATH
  2. being able to run/debug tests from IDE, possibly defining environment variable PYTHONPATH
  3. run test during CI (Travis)
  4. do not expose anything else than desired function
  5. expose a function
  6. being callable as a command line interface

Context

Here is my project structure. I like it that way but I'll gladly change it to satisfy above requirements. But I'd really prefer not to mix tests and sources or resources and code.

.
├── LICENSE
├── README.md
├── mysql_tracer
│   ├── __init__.py
│   ├── __main__.py
│   ├── chest.py
│   ├── cursor_provider.py
│   ├── query.py
│   └── writer.py
├── requirements.txt
├── setup.py
└── tests
    ├── assets
    │   ├── sample-query-executed.csv
    │   ├── sample-query-executed.sql
    │   ├── sample-query.sql
    │   └── sample-template-query.sql
    └── python
        ├── __init__.py
        ├── conftest.py
        ├── test_query.py
        └── test_writer.py

4 directories, 18 files

Development

During development, I set up 1, 2 and 3. I had a hard time doing it because import between modules, import from tests and mocking modules all seem to work differently. I managed a working solution with the following:

  • module query.py imports module writer.py using import writer
  • module query.py imports class CursorProvider from module cursor_provider.py using from cursor_provider import CursorProvider
  • module test_query.py imports query using import query
  • function test_result from module test_query.py mocks module CursorProvider imported from module query.py using @mock.patch('query.CursorProvider')
  • .travis.yml defines test as:
script:
- export PYTHONPATH=mysql_tracer
- pytest

That last piece was especially counter intuitive as previous setups I tried would make locale tests running from terminal without setting the environment variable and Idea would always manage to work somehow. The working setup required to set it for both Travis and the local terminal.

One could search my git history and see that I had to try many things before making it working:

$ git log 1e5338dfab429d894a109a287f2a087c5d945a90..39c6e093e3bdb4470f374c4eb50d02ec52cb878a --pretty=format:"%h%x09%ad%x09%s"
39c6e09 Mon Feb 11 01:15:04 2019 +0100  travis, just do it please
544e540 Mon Feb 11 01:00:17 2019 +0100  mark it work locally... we'll see for CI later
a38caaa Mon Feb 11 00:10:50 2019 +0100  combine regexes with or
7a1eb88 Sun Feb 10 23:43:35 2019 +0100  travis, you're the last one, just f* works please
042f7c3 Sun Feb 10 23:08:14 2019 +0100  travis, mock, pytest, I changed everything, are you happy now ?
79e45c9 Sun Feb 10 22:54:35 2019 +0100  travis, get your shit together please
17eafaa Sun Feb 10 22:48:39 2019 +0100  another way to expose and import module to please both travis and mock
ea93558 Sun Feb 10 22:39:05 2019 +0100  test
4956d5a Sun Feb 10 21:34:31 2019 +0100  another way to expose Query...
98ff216 Sun Feb 10 21:22:33 2019 +0100  import differently
2426a6d Sun Feb 10 21:20:30 2019 +0100  expose Query
851ea43 Sun Feb 10 21:17:09 2019 +0100  setup.py

Packaging

But now that development is about to reach conclusion, I've set up the package configuration to support 5 & 6. Unfortunately, I didn't manage to set up 4 correctly and I broke 1, 2 and 3.

I had to change the imports between modules because otherwise using my package, after installing it locally or from pypi, would not work.

  • Rather than doing import writer I would do from . import writer
  • Rather than doing from cursor_provider import CursorProvider I would do from .cursor_provider import CursorProvider (mind the dot before the module name)

This made my cli interface work. After that I could use

$ mysql_tracer -h
... skipped output ...
$ mysql_tracer --host 127.0.0.1 --user aho --database company --destination /home/aho/Downloads /home/aho/sources/sql-test-tools/people.sql

At this point, importing the module mysql_tracer would work but it exposes nothing. However adding the following line to the mysql_tracer/__init__.py file will expose the class Query:

from .query import Query

But so are all modules defined within package mysql_tracer (end of list):

>>> import mysql_tracer
>>> dir(mysql_tracer)
['Query', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'chest', 'cursor_provider', 'query', 'writer']

While it is not bothering too much, it is undesired. I only meant the attribute Query to be available. chest, cursor_provider, query and writer should be hidden. Please notice they were not available before my tentative to expose class Query.

But now, the import statements that made the package features work make the tests fail:

    from . import writer
E   ImportError: attempted relative import with no known parent package

So, How can I keep the features (5 & 6) working while restoring development requirements (1,2 & 3) ? And possibly also the feature 4 ?

I hope I provided necessary and sufficient information but if you need more, please visit my project, package or CI page or comment and ask for clarification.

Adrien H
  • 643
  • 6
  • 21
  • 1
    Remove `tests/python/__init__.py`; move `tests/python/conftest.py` to project root (`.`); use absolute imports in code (e.g. `from mysql_tracer.query import Query` etc). This should fix the import issues and will avoid the `PYTHONPATH` adjustments. BTW a well-written, nicely formatted question with full information provided, well done! – hoefling Feb 18 '19 at 08:38
  • I confirmed 1, 2, 3 and 5 working. I couldn't confirm 4 and 6 but that may be because of my work computer python installation (anaconda, venv and stuff). I'll test later on my home computer. I've pushed the code following your advice on github and test pypi : https://test.pypi.org/project/mysql-tracer/ . – Adrien H Feb 18 '19 at 10:45
  • Ok, I got 6 kind of working, I can call the script using `python3 -m mysql_tracer -h`. So I consider it working. All that is left is now 4. – Adrien H Feb 18 '19 at 13:17
  • I'm not quite sure what you mean with exposing - all the packaged code will be importable in the interpreter, if that's what you mean. BTW why not introducing a custom execjtable `mysql_tracer` (via an entry point) so one can run `mysql_tracer -h` directly? – hoefling Feb 19 '19 at 19:59
  • @hoefling I just made an entry point since the last comment. it's just weird that it used to work without it. By exposing I mean that someone that imports my module should only see the attributes that I choose he should see. I've updated my post to make that explicit. Anyway, the present solution is good. You can write an answer with the content of your first comment and the instruction to create an entry point (console script). – Adrien H Feb 20 '19 at 08:08
  • Ok, so after searching documentation more, it seems it's not easily possible. I achieve that behaviour in a previous iteration but that was only a coincidence. Other SO post advice to put a leading underscore in package name or to put them in a sub package named private. https://stackoverflow.com/questions/3602110/python-private-module-in-a-package I mixed it with the other issues because I "broke" this feature 4 when doing the other features. – Adrien H Feb 20 '19 at 08:45

0 Answers0