Python – Obtener ruta de la estructura del proyecto raíz

Tengo un proyecto de Python con un archivo de configuración en la raíz del proyecto. Se debe acceder al archivo de configuración en unos pocos archivos diferentes a lo largo del proyecto.

Así que parece algo así: /configuration.conf /A/a.py , /A/B/b.py (cuando b, a.py accede al archivo de configuración).

¿Cuál es la forma mejor / más fácil de obtener la ruta a la raíz del proyecto y al archivo de configuración sin depender de qué archivo del proyecto en el que me encuentro? Es decir, sin usar ../../ ? Está bien asumir que conocemos el nombre de la raíz del proyecto.

Puede hacer esto como lo hace Django: definir una variable a la raíz del proyecto a partir de un archivo que se encuentra en el nivel superior del proyecto. Por ejemplo, si esto es lo que parece la estructura de su proyecto:

 project/ configuration.conf definitions.py main.py utils.py 

En definitions.py puede definir (esto requiere sistema import os ):

 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) # This is your Project Root 

Por lo tanto, con la raíz del proyecto conocida, puede crear una variable que apunte a la ubicación de la configuración (esto se puede definir en cualquier lugar, pero un lugar lógico sería ubicarlo en una ubicación donde se definan las constantes, por ejemplo, definitions.py ) :

 CONFIG_PATH = os.path.join(ROOT_DIR, 'configuration.conf') # requires `import os` 

Luego, puede acceder fácilmente a la constante (en cualquiera de los otros archivos) con la statement de importación (por ejemplo, en utils.py ): from definitions import CONFIG_PATH .

Para obtener la ruta del módulo “raíz”, puede utilizar:

 import os import sys os.path.dirname(sys.modules['__main__'].__file__) 

Pero lo que es más interesante es que si tiene un “objeto” de configuración en su módulo más importante, podría leerlo de este modo:

 app = sys.modules['__main__'] stuff = app.config.somefunc() 

Otras respuestas de consejos para utilizar el archivo en el nivel superior del proyecto. Esto no es necesario si utiliza pathlib.Path y parent . Considere la siguiente estructura de directorios donde se han omitido todos los archivos excepto README.md y utils.py .

 project │ README.md | └───src │ │ utils.py | | ... | ... 

En utils.py definimos la siguiente función.

 from pathlib import Path def get_project_root() -> Path: """Returns project root folder.""" return Path(__file__).parent.parent 

En cualquier módulo del proyecto, ahora podemos obtener la raíz del proyecto de la siguiente manera.

 from src.utils import get_project_root root = get_project_root() 

Beneficios : cualquier módulo que llame a get_project_root se puede mover sin cambiar el comportamiento del progtwig. Solo cuando el módulo utils.py se mueve, tenemos que actualizar get_project_root y las importaciones (use la refactorización del IDE para automatizar esto).

Una forma estándar de lograr esto sería utilizar el módulo pkg_resources que forma parte del paquete setuptools . setuptools se usa para crear un paquete de Python que se puede instalar.

Puede usar pkg_resources para devolver el contenido del archivo deseado como una cadena y puede usar pkg_resources para obtener la ruta real del archivo deseado en su sistema.

Digamos que usted tiene un paquete llamado stackoverflow .

 stackoverflow/ |-- app | `-- __init__.py `-- resources |-- bands | |-- Dream\ Theater | |-- __init__.py | |-- King's\ X | |-- Megadeth | `-- Rush `-- __init__.py 3 directories, 7 files 

Ahora digamos que desea acceder al archivo Rush desde un módulo app.run . Use pkg_resources.resouces_filename para obtener la ruta a Rush y pkg_resources.resource_string para obtener el contenido de Rush; por lo tanto:

 import pkg_resources if __name__ == "__main__": print pkg_resources.resource_filename('resources.bands', 'Rush') print pkg_resources.resource_string('resources.bands', 'Rush') 

La salida:

 /home/sri/workspace/stackoverflow/resources/bands/Rush Base: Geddy Lee Vocals: Geddy Lee Guitar: Alex Lifeson Drums: Neil Peart 

Esto funciona para todos los paquetes en tu ruta de python. Entonces, si quieres saber dónde existe lxml.etree en tu sistema:

 import pkg_resources if __name__ == "__main__": print pkg_resources.resource_filename('lxml', 'etree') 

salida:

 /usr/lib64/python2.7/site-packages/lxml/etree 

El punto es que puede usar este método estándar para acceder a los archivos que están instalados en su sistema (por ejemplo, pip install xxx o yum -y install python-xxx) y los archivos que se encuentran dentro del módulo en el que está trabajando actualmente.

Todas las soluciones anteriores parecen ser demasiado complicadas para lo que creo que necesitas, y a menudo no funcionaron para mí. El siguiente comando de una línea hace lo que quieres:

 import os ROOT_DIR = os.path.abspath(os.curdir) 

Esto me funcionó utilizando un proyecto estándar de PyCharm con mi entorno virtual (venv) en el directorio raíz del proyecto.

El código a continuación no es el más bonito, pero siempre obtiene la raíz del proyecto. Devuelve la ruta completa del directorio a venv desde la variable de entorno VIRTUAL_ENV , por ejemplo, /Users/NAME/documents/PROJECT/venv

Luego divide la ruta en la última / , dando una matriz con dos elementos. El primer elemento será la ruta del proyecto, por ejemplo, /Users/NAME/documents/PROJECT

 import os print(os.path.split(os.environ['VIRTUAL_ENV'])[0]) 

Recientemente he estado tratando de hacer algo similar y encontré estas respuestas inadecuadas para mis casos de uso (una biblioteca distribuida que necesita detectar la raíz del proyecto). Principalmente he estado luchando contra diferentes entornos y plataformas, y aún no he encontrado algo perfectamente universal.

Código local al proyecto

He visto este ejemplo mencionado y usado en algunos lugares, Django, etc.

 import os print(os.path.dirname(os.path.abspath(__file__))) 

Por simple que sea, solo funciona cuando el archivo en el que se encuentra el fragmento es en realidad parte del proyecto. No recuperamos el directorio del proyecto, sino el directorio del fragmento

De manera similar, el enfoque de sys.modules se descompone cuando se llama desde fuera del punto de entrada de la aplicación, específicamente observé que un hilo secundario no puede determinar esto sin relación con el módulo ” principal “. Puse explícitamente la importación dentro de una función para demostrar una importación desde un subproceso secundario, moviéndolo al nivel superior de app.py lo arreglaría.

 app/ |-- config | `-- __init__.py | `-- settings.py `-- app.py 

app.py

 #!/usr/bin/env python import threading def background_setup(): # Explicitly importing this from the context of the child thread from config import settings print(settings.ROOT_DIR) # Spawn a thread to background preparation tasks t = threading.Thread(target=background_setup) t.start() # Do other things during initialization t.join() # Ready to take traffic 

settings.py

 import os import sys ROOT_DIR = None def setup(): global ROOT_DIR ROOT_DIR = os.path.dirname(sys.modules['__main__'].__file__) # Do something slow 

Ejecutar este progtwig produce un error de atributo:

 >>> import main >>> Exception in thread Thread-1: Traceback (most recent call last): File "C:\Python2714\lib\threading.py", line 801, in __bootstrap_inner self.run() File "C:\Python2714\lib\threading.py", line 754, in run self.__target(*self.__args, **self.__kwargs) File "main.py", line 6, in background_setup from config import settings File "config\settings.py", line 34, in  ROOT_DIR = get_root() File "config\settings.py", line 31, in get_root return os.path.dirname(sys.modules['__main__'].__file__) AttributeError: 'module' object has no attribute '__file__' 

… por lo tanto, una solución basada en subprocesos

Ubicación independiente

Usando la misma estructura de aplicación que antes, pero modificando settings.py

 import os import sys import inspect import platform import threading ROOT_DIR = None def setup(): main_id = None for t in threading.enumerate(): if t.name == 'MainThread': main_id = t.ident break if not main_id: raise RuntimeError("Main thread exited before execution") current_main_frame = sys._current_frames()[main_id] base_frame = inspect.getouterframes(current_main_frame)[-1] if platform.system() == 'Windows': filename = base_frame.filename else: filename = base_frame[0].f_code.co_filename global ROOT_DIR ROOT_DIR = os.path.dirname(os.path.abspath(filename)) 

Desglosando esto: primero queremos encontrar con precisión el ID de hilo del hilo principal. En Python3.4 + la biblioteca de subprocesos tiene threading.main_thread() , sin embargo, todo el mundo no usa 3.4+, por lo que buscamos en todos los subprocesos buscando el hilo principal para guardar su ID. Si el subproceso principal ya ha salido, no aparecerá en la lista en threading.enumerate() . RuntimeError() un RuntimeError() en este caso hasta que encuentre una solución mejor.

 main_id = None for t in threading.enumerate(): if t.name == 'MainThread': main_id = t.ident break if not main_id: raise RuntimeError("Main thread exited before execution") 

A continuación encontramos el primer cuadro de stack del hilo principal. Usando la función específica de sys._current_frames() obtenemos un diccionario del marco de stack actual de cada hilo. Luego, utilizando inspect.getouterframes() podemos recuperar la stack completa para el hilo principal y el primer fotogtwig. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Finalmente, las diferencias entre las implementaciones de inspect.getouterframes() Windows y Linux deben manejarse. Usando el nombre de archivo limpiado, os.path.abspath() y os.path.dirname() limpian las cosas.

 if platform.system() == 'Windows': filename = base_frame.filename else: filename = base_frame[0].f_code.co_filename global ROOT_DIR ROOT_DIR = os.path.dirname(os.path.abspath(filename)) 

Hasta ahora he probado esto en Python2.7 y 3.6 en Windows, así como en Python3.4 en WSL

Tratar:

 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 

Luché con este problema también hasta que llegué a esta solución. Esta es la solución más limpia en mi opinión.

En su setup.py agregue “paquetes”

 setup( name='package_name' version='0.0.1' . . . packages=['package_name'] . . . ) 

En tu python_script.py

 import pkg_resources import os resource_package = pkg_resources.get_distribution( 'package_name').location config_path = os.path.join(resource_package,'configuration.conf')