Setting up Python path during development - python

I work on a couple of different programs and packages in Python. They are each developed in their own Git repository, but frequently need to import modules defined in other packages. For instance, during development, the directory structure looks something like:
src/
|-- project-a/
| |-- client.py
| |-- server.py
| |-- package-a
| |-- __init__.py
| |-- module.py
|-- project-b/
| |-- package-b
| | |-- __init__.py
| | |-- other_module.py
| |-- package-c
| |-- __init__.py
| |-- third_module.py
|-- project-c/
|-- server1.py
|-- server2.py
|-- package-d/
|-- package-e/
|-- package-f/
When they are all installed, they work fine; they are all installed such that each package is in your Python path, and you can import from them as you need.
However, in development, I want the development version of each of these to be in my Python path, not the installed version. When making changes, I don't want to have to install each package that I'm changing to test it, I want the changes to take effect immediately. That means my Python path needs to include the directories project-a, project-b, etc.
Our current solution is just to have an environment.bash in the top level, which you source in your shell and it sets PYTHONPATH. That works OK, but I frequently forget to do so; since this is a client server application, with communications between servers, I need to have at least four windows open to different VMs to run this, and it happens pretty often that I forget to source environment.bash in at least one of those, leading me to try debugging strange behavior until I realize I'm importing the wrong things.
Another solution would be to set sys.path from within the top level client.py or server.py. This would work fine for launching them directly, but I would also need the path set up for running tools like Pylint or Sphinx, which that solution wouldn't cover. I'd also need a way to distinguish between running from source (when I want the path to include . and ../project-b) and running the installed version (which should use the standard path without modification).
Another choice would be to have a Makefile which sets up PYTHONPATH appropriately for various targets like make run-server, make lint, make doc, and so on. That's OK for those targets, which don't require any options, but would be inconvenient for running the client, which takes arguments. make run-client ARGS='foo bar' is a fairly cumbersome way to invoke it.
Is there any common way of setting up the Python path during development so that both my executables and tools like Pylint and Sphinx can pick it up appropriately, without interfering with how it will behave when installed?

A straightforward solution would be to simply symlink in the directories for each module in a separate folder, and run things from there. That way Python sees them all being in the same location, even though the actual sources are in different repositories.
src/
|-- project-a/
| |-- client.py
| |-- server.py
| |-- package-a
| |-- __init__.py
| |-- module.py
|-- project-b/
|-- package-b
| |-- __init__.py
| |-- other_module.py
|-- package-c
|-- __init__.py
|-- third_module.py
run/
|-- client.py --> ../src/project-a/client.py
|-- server.py --> ../src/project-a/server.py
|-- package-a/ --> ../src/project-a/package-a/
|-- package-b/ --> ../src/project-b/package-b/
|-- package-c/ --> ../src/project-b/package-c/

Related

Why import works in this ROS python script

I am working with ROS packages and coming from this tutorial. This import statement surprisingly works given the absence of AddTwoIntsResponse anywhere in the current working directory or any other directory listed in PATH. Also how come a .srv gets imported?
# add_two_ints_server.py
from beginner_tutorials.srv import AddTwoInts, AddTwoIntsResponse
The current working directory is ~/catkin_ws/src/beginner_tutorials/srv
This is my ROS directory layout:
catkin_ws
|-- src
| `-- beginner_tutorials
| |-- scripts
| | `-- add_two_ints_server.py
| `-- srv
| `-- AddTwoInts.srv
|-- build
`-- devel
The contents of AddTwoInts.srv are:
int64 a
int64 b
---
int64 sum
According to my understanding this should throw an ImportError: cannot import name 'AddTwoIntsResponse', but it doesn't. Importing any other file say: from beginner_tutorials.srv import foo throws an ImportError.
Where is my understanding going wrong?
|-- src
| `-- beginner_tutorials
| |-- scripts
| | `-- add_two_ints_server.py
| `-- srv
| `-- AddTwoInts.srv
|-- build
|-- devel // this is where your modules are imported from
When you build the package using catkin_make, 'catkin` generates the relevant python files for your service type defined in .srv file and puts them under catkin_ws/devel/lib/your-python-version/dist-packages/package-name/srv.
If your workspace is sourced, catkin_ws/devel/lib/your-python-version/dist-packages/ is already added to your PYTHONPATH and that is how you are able to import them successfully.
In case of the tutorial package that you are using, imports may work even when you haven't sourced your current catkin-directory, if you have the binaries of the tutorials installed. This way the python modules reside under /opt/ros/ros-version/lib/your-python-version/dist-packages/ and that is again part of the PYTHONPATH. (If ROS env is available)

How to import modules in Python framework with multiple subfolder

My question might have discussed in earlier posts too, but couldn't get proper answer applicable for my scenario. Hence posting it as a new query.
I have multiple sub-folders where module tries to import modules present across multiple subfolders.
Folder Structure:
main
|-- lib
| |-- lib1.py
| |-- lib2.py
| `-- lib3.py
|-- common
| |-- lib4.py
|-- tests
| |--folder1
| |-- script1.py
| |-- folder2
| |-- script2.py
| |--scriptA.py
| `--scriptB.py
Use case/Requirements:
script1 & script2 import functions from module lib1.py.
lib1 wants to import functions from lib2.py & lib3.py
lib4.py import funtions from lib1 & lib2
I tried adding blank __init__.py in root folder (main) and the all other subfolders. But couldn't get this working. Ending up with 'ModuleNotFound' error.
You need blank __init__.py files, not init.py. See the python documentation for more info.

PyGTK Builder add_from_file() different path approach with PyCharm

Well, in my app/view.py there's the a function call to GTK.Builder().add_from_file("AppView.glade"), however I observed it has been interpreted differently depending of how I execute the code.
project
|-- app
| |-- __init__.py
| |-- model.py
| |-- view.py
| |-- ...
|
|-- test
| |-- __init__.py
| |-- model.py
| |-- ...
|
|-- ui
|-- AppView.glade
When I regularly execute (python3 -m app) or test (python3 -m unittest test) the project, the add_from_file function expect the relative path from project root, that is ui/AppView.glade.
However, when I run or debug from PyCharm project's test or execution it expect a relative path from the file is calling the function, for this case `../ui/AppView.
Could I change this PyCharm behavior?

Why do some Python packages have repetitive directory names?

The question of what the directory structure of a Python project has been asked a number of times on Stack Overflow (e.g. here, here and here)
And many answers are given. But one thing that doesn't seem to be clear in any of those answers is why some projects have repetitive directories. For example, in this article which is often cited, the suggested layout is:
<root>/
|-- Twisted/
| |-- __init__.py
| |-- README
| |-- setup.py
| |-- twisted/
| | |-- __init__.py
| | |-- main.py
| | |-- test/
| | | |-- __init__.py
| | | |-- test_main.py
| | | |-- test_other.py
| | |-- bin/
| | | |-- myprogram
In this example, /Twisted/twisted/main.py is the main file
But then on the other hand you have advice like this:
Many developers are structuring their repositories poorly due to the new bundled application templates.
<root>/
|-- samplesite/
| |-- manage.py
| |-- samplesite/
| | |-- settings.py
| | |-- wsgi.py
| | |-- sampleapp/
| | |-- models.py
Dont do this.
Repetitive paths are confusing for both your tools and your developers. Unnecessary nesting doesnt help anybody. Let's do it properly:
<root>/
|-- manage.py
|-- samplesite/
| |-- settings.py
| |-- wsgi.py
| |-- sampleapp/
| |-- models.py
My question is not necessarily "which way is better?", since there may be pros or cons to each way.
Instead, my question is, if I go with the more simplified second style, what will I lose? Is there a good reason to have a /<root>/Twisted/twisted/main.py directory structure rather than just /<root>/twisted/main.py ? Does it make it easier somehow to share my application or make the import process smoother? Something else?
I believe the most common layout of python projects is something like this:
project/
|-- setup.py
|-- bin/
|-- docs/ ...
|-- examples/ ...
|-- package/
|-- __init__.py
|-- module1.py
|-- module2.py
|-- subpackage/ ...
|-- tests/ ...
Where the project is the name of the project and the package is the name of the top level import, for example scikits-learn and sklearn. The package has everything that python should be able to import, and you import using the package name. For example from package import thing or from package.module1 import thing. The project has the package and any supporting things like docs, examples and installation scripts. Notice that there is typically no __init__.py in project because project is not python importable. It is common for the project and package to have the same name, but not required.
Those two documents are closer than you think. Both Interesting Things, Largely Python and Twisted Related (your first example) and the django-admin startproject docs assume you are outside of the project repository while Structuring Your Project (your second example) assumes you are inside the repository. To quote, "Well, they go to their bare and fresh repository and run the following...".
The django docs state that if you run
django-admin.py start-project samplesite
both the project directory and project package will be named and the project directory will be created in the current
working directory
The command creates the project directory for you, so you certainly shouldn't be inside of an already-created project directory when you run it. The docs go on to say
django-admin startproject myproject /Users/jezdez/Code/myproject_repo
If the optional destination is provided, Django will use that existing
directory as the project directory
Now, suppose you were already in /Users/jezdez/Code/myproject_repo. Then you would do
django-admin startproject myproject .
to create the project package in the current directory. Voila, you've got the second author's example! The author was really just telling you to avoid the first form if you are creating your repo before running the command.
So, lets redraw your directory structure. In the first example, <root> is the directory where you hold your dev repos. Twisted is the directory with your repo. (As an aside, that directory shouldn't have an __init__.py because its not a package directory). In the final example, <root> is the repo directory itself. Supposing I named that directory DjangoExample, then the structure would be
<root>
|-- Twisted/
| |-- __init__.py
| |-- README
| |-- setup.py
| |-- twisted/
| | |-- __init__.py
| | |-- main.py
| | |-- test/
| | | |-- __init__.py
| | | |-- test_main.py
| | | |-- test_other.py
| | |-- bin/
| | | |-- myprogram
|
|-- DjangoExample/
| |-- manage.py
| |-- samplesite/
| | |-- settings.py
| | |-- wsgi.py
| | |-- sampleapp/
| | |-- models.py
As for other differences, the django app has to follow the django framework rules whereas twised follows the more generic python package rules.

Make rope project aware of core libraries

I'm working on a large code base and I'd like to set up rope projects so that rope is fast and does what I want. For what it's worth, I'm using rope with emacs, but if I understand correctly, rope's behavior should be independent of the editor.
The code base has many core libraries used by many apps. Each app depends on one or more core libraries but never on another app. Here's a simplified representation of the directory structure:
repo
|-- core
| |--CoreLib1
| | |-- CoreLib1.egg-info
| | `-- library_module
| | |-- __init__.py
| | `-- lib.py
| `--CoreLib2
| |-- CoreLib2.egg-info
| `-- library_module
| |-- __init__.py
| `-- lib.py
`-- apps
|-- AppA
| |-- AppA.egg-info
| `-- app_a_module
| |-- __init__.py
| `-- src.py
|-- AppB
| |-- AppB.egg-info
| `-- app_b_module
| |-- __init__.py
| `-- src.py
`-- AppC
|-- AppC.egg-info
`-- app_c_module
|-- __init__.py
`-- src.py
What I want to do
Currently I have repo/.ropeproject, and rope behaves how I want, but is slow. I believe the slowness is because it's analyzing all the code in all the apps at any given time. To solve this, I'm trying to create a rope project in each app (e.g. /repo/apps/AppA/.ropeproject) that knows about core but doesn't know about the other apps. The problem is, I can't get it to know about core. This means I can't do any rope operations on any names from core.
Works, but is slow:
Make sure there are no .ropeproject directories in the whole code base. Create a rope project in /repo/.
Put the following code in /repo/.ropeproject/config.py:
src_dirs = [
dirpath
for dirpath, dirnames, filenames in os.walk('core/')
if any(map(lambda dirname: dirname.endswith('.egg-info'), dirnames))
]
for src_dir in src_dirs:
prefs.add('python_path', src_dir)
Reload the rope project (to make sure it's using the freshly updated config.py)
Rope Generate Autoimport Cache. This takes upwards of 60 seconds and can't run in the background.
While editing repo/apps/AppA/app_a_module/src.py, attempt to use rope to auto-import a name from core. It works.
Fast, but doesn't work:
Make sure there are no .ropeproject directories in the whole code base. Create a rope project in /repo/apps/AppA/.
Put the following code in apps/AppA/.ropeproject/config.py:
src_dirs = [
dirpath
for dirpath, dirnames, filenames in os.walk('../../core/')
if any(map(lambda dirname: dirname.endswith('.egg-info'), dirnames))
]
for src_dir in src_dirs:
prefs.add('python_path', src_dir)
Reload the rope project (to make sure it's using the freshly updated config.py)
Rope Generate Autoimport Cache. This takes less than 1 second.
While editing repo/apps/AppA/app_a_module/src.py, attempt to use rope to auto-import a name from core. It fails.
Is what I want to do within the scope of rope's capabilities? Should it be able to work? If so, what am I doing wrong?

Categories