Python - Récupère le chemin de la structure du projet racine

128

J'ai un projet python avec un fichier de configuration à la racine du projet. Le fichier de configuration doit être accessible dans quelques fichiers différents tout au long du projet.

Il semble donc quelque chose comme: <ROOT>/configuration.conf <ROOT>/A/a.py, <ROOT>/A/B/b.py(quand b, accès a.py le fichier de configuration).

Quelle est la meilleure / la plus simple façon d'obtenir le chemin vers la racine du projet et le fichier de configuration sans dépendre du fichier dans le projet dans lequel je suis? c'est à dire sans utiliser ../../? Il est normal de supposer que nous connaissons le nom de la racine du projet.

Shookie
la source
<ROOT>/__init__.pyn'existe?
mgilson
Soit votre fichier de configuration est un module python, et vous pouvez facilement y accéder simplement avec une instruction d'importation, soit ce n'est pas un module python et vous devez le placer dans un emplacement bien connu. Par exemple $ HOME / .my_project / my_project.conf.
John Smith Facultatif
@JohnSmithOptional - C'est un fichier JSON. Je dois pouvoir y accéder en utilisant le chemin. Oui. Tous les dossiers l'incluent.
Shookie
_ Il est normal de supposer que nous connaissons le nom de la racine du projet._ Cela signifie-t-il que vous connaissez le chemin d'accès au projet? N'est-ce pas juste os.path.join (nom_racine_connue, "configuration.conf") alors?
tdelaney
Si c'est une configuration utilisateur, j'utiliserais généralement quelque chose comme os.path.expanduser('~/.myproject/myproject.conf'). Cela fonctionne sous Unix et Windows.
John Smith Facultatif

Réponses:

159

Vous pouvez faire ceci comme Django le fait: définir une variable à la racine du projet à partir d'un fichier qui se trouve au niveau supérieur du projet. Par exemple, si voici à quoi ressemble la structure de votre projet:

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

Dans definitions.pyvous pouvez définir (cela nécessite import os):

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

Ainsi, avec la racine du projet connue, vous pouvez créer une variable qui pointe vers l'emplacement de la configuration (cela peut être défini n'importe où, mais un endroit logique serait de le placer dans un emplacement où les constantes sont définies - par exemple definitions.py):

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

, Vous pouvez facilement accéder ensuite la constante (dans l' un des autres fichiers) avec l'instruction d'importation (par exemple utils.py): from definitions import CONFIG_PATH.

jrd1
la source
1
Pour inclure le fichier definitions.py comme ça, sera-t-il également nécessaire d'ajouter un __init__.pyfichier au répertoire racine du projet? Cela devrait-il être correct? Je viens de commencer avec python et je ne suis pas sûr des meilleures pratiques. Merci.
akskap
3
@akskap: Non, un __init__.pyne sera pas requis, car ce fichier n'est requis que lors de la définition des packages: Les __init__.pyfichiers sont nécessaires pour que Python traite les répertoires comme contenant des packages; ceci est fait pour empêcher les répertoires avec un nom commun, tel que string, de masquer involontairement les modules valides qui surviennent plus tard sur le chemin de recherche du module. Dans le cas le plus simple, il __init__.pypeut s'agir simplement d'un fichier vide, mais il peut également exécuter le code d'initialisation du package ou définir la __all__variable, décrite plus loin. Voir: docs.python.org/3/tutorial/modules.html#packages
jrd1
Je suis curieux, du point de vue du style, s'il est acceptable ou mal vu d'ajouter ces définitions au __init.py__paquet racine. Cela éviterait de créer un autre fichier et autoriserait la syntaxe plus agréable de from root_pack import ROOT_DIR, CONFIG_PATH.
Johndt6
@ Johndt6: la convention est de rester __init__.pyvide, mais ce n'est pas strictement vrai (c'est une convention après tout). Voir ceci pour plus: stackoverflow.com/questions/2361124/using-init-py
jrd1
1
@JavNoor: non - dans l'exemple que vous avez cité, os.path.abspathappelle une chaîne, '__file__'. Rappelez-vous qu'il __file__s'agit en fait d'un attribut d'importation défini pour les modules Python. Dans ce cas, __file__retournera le chemin à partir duquel le module est chargé. En savoir plus ici (voir la section des modules): docs.python.org/3/reference/datamodel.html
jrd1
62

D'autres réponses conseillent d'utiliser un fichier au plus haut niveau du projet. Cela n'est pas nécessaire si vous utilisez pathlib.Pathet parent(Python 3.4 et plus). Considérez la structure de répertoires suivante où tous les fichiers sauf README.mdet utils.pyont été omis.

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

Dans utils.pynous définissons la fonction suivante.

from pathlib import Path

def get_project_root() -> Path:
    return Path(__file__).parent.parent

Dans n'importe quel module du projet, nous pouvons maintenant obtenir la racine du projet comme suit.

from src.utils import get_project_root

root = get_project_root()

Avantages : Tout module qui appelle get_project_rootpeut être déplacé sans changer le comportement du programme. Ce n'est que lorsque le module utils.pyest déplacé que nous devons mettre à jour get_project_rootet importer (des outils de refactoring peuvent être utilisés pour automatiser cela).

RikH
la source
2
Tout module qui se trouve à la racine. Appeler src.utils depuis l'extérieur de la racine ne devrait pas fonctionner. Ai-je tort?
aerijman
le nom ' fichier ' n'est pas défini, pourquoi?
Luk Aron
26

Toutes les solutions précédentes semblent être trop compliquées pour ce dont je pense que vous avez besoin, et souvent ne fonctionnaient pas pour moi. La commande en une ligne suivante fait ce que vous voulez:

import os
ROOT_DIR = os.path.abspath(os.curdir)
Martim
la source
3
Mettez ça dans config.py, à la racine du répertoire, .. bamn! Vous avez un singleton.
swdev
2
Cette méthode suppose que vous exécutez l'application à partir du chemin d'accès qu'elle existe. De nombreux "utilisateurs" ont une icône sur laquelle ils cliquent sur un bureau ou peuvent exécuter l'application à partir d'un autre répertoire entièrement.
DevPlayer
23

Pour obtenir le chemin du module "root", vous pouvez utiliser:

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

Mais plus intéressant encore, si vous avez un "objet" de configuration dans votre module le plus haut, vous pouvez le lire comme ceci:

app = sys.modules['__main__']
stuff = app.config.somefunc()
DevPlayer
la source
1
Ici osn'est pas disponible par défaut. Besoin d'importer os. Donc, ajouter la ligne import osrendrait la réponse plus complète.
Md. Abu Nafee Ibna Zahid
5
Cela donne le répertoire qui contient le script qui a été exécuté. Par exemple, lors de l'exécution, python3 -m topmodule.submodule.scriptil donnera /path/to/topmodule/submoduleau lieu de /path/to/topmodule.
danijar
14

Une manière standard d'y parvenir serait d'utiliser le pkg_resourcesmodule qui fait partie du setuptoolspackage. setuptoolsest utilisé pour créer un package python installable.

Vous pouvez utiliser pkg_resourcespour renvoyer le contenu de votre fichier souhaité sous forme de chaîne et vous pouvez utiliser pkg_resourcespour obtenir le chemin réel du fichier souhaité sur votre système.

Disons que vous avez un package appelé stackoverflow.

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

3 directories, 7 files

Supposons maintenant que vous souhaitiez accéder au fichier Rush à partir d'un module app.run. Utilisez pkg_resources.resouces_filenamepour obtenir le chemin vers Rush et pkg_resources.resource_stringpour obtenir le contenu de Rush; ainsi:

import pkg_resources

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

Le résultat:

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

Cela fonctionne pour tous les packages de votre chemin python. Donc, si vous voulez savoir où lxml.etreeexiste sur votre système:

import pkg_resources

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

production:

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

Le fait est que vous pouvez utiliser cette méthode standard pour accéder aux fichiers qui sont installés sur votre système (par exemple, pip install xxx ou yum -y install python-xxx) et aux fichiers qui se trouvent dans le module sur lequel vous travaillez actuellement.

musaraigne
la source
1
J'aime ton choix de groupe!
dylan_fan
4

Essayer:

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
harry
la source
1
C'est exactement ce dont j'avais besoin. Solution simple, fonctionne pour moi car ma structure était root-> config-> conf.py Je voulais définir la racine du projet dans conf.py et root était exactement deux niveaux au-dessus de ce fichier.
Daniyal Arshad
4

Sous le code Renvoie le chemin jusqu'à la racine de votre projet

import sys
print(sys.path[1])
Arpan Saini
la source
Bon conseil! Je me demande pourquoi personne n'a voté pour votre réponse à part moi: P
daveoncode
Merci Daveon Vraiment apprécié !!
Arpan Saini le
Malheureusement, ce n'est pas ça, simple: P ... jetez un œil à ma solution complète: stackoverflow.com/a/62510836/267719
daveoncode
2

J'ai également lutté avec ce problème jusqu'à ce que j'arrive à cette solution. C'est la solution la plus propre à mon avis.

Dans votre setup.py ajoutez "packages"

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

Dans votre 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')
Gars
la source
Utiliser un environnement virtuel et installer le paquet avec python3 setup.py installcelui-ci ne pointait plus vers le dossier du code source, mais vers l'œuf à l'intérieur ~./virtualenv/..../app.egg. J'ai donc dû inclure le fichier de configuration dans l'installation du package.
loxosceles
2

Juste un exemple: je veux exécuter runio.py depuis helper1.py

Exemple d'arborescence de projet:

myproject_root
- modules_dir/helpers_dir/helper1.py
- tools_dir/runio.py

Obtenez la racine du projet:

import os
rootdir = os.path.dirname(os.path.realpath(__file__)).rsplit(os.sep, 2)[0]

Construire le chemin vers le script:

runme = os.path.join(rootdir, "tools_dir", "runio.py")
execfile(runme)
Alex Granovsky
la source
1

Cela a fonctionné pour moi en utilisant un projet PyCharm standard avec mon environnement virtuel (venv) sous le répertoire racine du projet.

Le code ci-dessous n'est pas le plus joli, mais obtient systématiquement la racine du projet. Il renvoie le chemin complet du répertoire vers venv à partir de la VIRTUAL_ENVvariable d'environnement par exemple/Users/NAME/documents/PROJECT/venv

Il divise ensuite le chemin en dernier /, donnant un tableau avec deux éléments. Le premier élément sera le chemin du projet, par exemple/Users/NAME/documents/PROJECT

import os

print(os.path.split(os.environ['VIRTUAL_ENV'])[0])
Gaz_Edge
la source
3
Cela ne fonctionnera pas avec des configurations comme anaconda ou pipenv, car l'environnement virtuel n'est pas contenu dans le projet dans ces cas.
Gripp
1

J'ai récemment essayé de faire quelque chose de similaire et j'ai trouvé ces réponses inadéquates pour mes cas d'utilisation (une bibliothèque distribuée qui doit détecter la racine du projet). J'ai principalement combattu différents environnements et plates-formes, et je n'ai toujours pas trouvé quelque chose de parfaitement universel.

Code local au projet

J'ai vu cet exemple mentionné et utilisé dans quelques endroits, Django, etc.

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

Aussi simple que cela soit, cela ne fonctionne que lorsque le fichier dans lequel se trouve l'extrait de code fait réellement partie du projet. Nous ne récupérons pas le répertoire du projet, mais plutôt le répertoire de l'extrait de code

De même, l' approche sys.modules tombe en panne lorsqu'elle est appelée depuis l'extérieur du point d'entrée de l'application, en particulier, j'ai observé qu'un thread enfant ne peut pas le déterminer sans rapport avec le module « principal ». J'ai explicitement mis l'importation dans une fonction pour démontrer une importation à partir d'un thread enfant, le déplacer vers le niveau supérieur de app.py le corrigerait.

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

L'exécution de ce programme produit une erreur d'attribut:

>>> 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 <module>
    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__'

... d'où une solution basée sur le threading

Indépendant de l'emplacement

Utilisation de la même structure d'application qu'avant mais modification de 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))

Décomposer ceci: Nous voulons d'abord trouver avec précision l'ID de thread du thread principal. Dans Python3.4 +, la bibliothèque de threads a threading.main_thread()cependant, tout le monde n'utilise pas 3.4+ donc nous recherchons dans tous les threads à la recherche du thread principal, sauf son ID. Si le thread principal est déjà terminé, il ne sera pas répertorié dans le threading.enumerate(). Nous soulevons un RuntimeError()dans ce cas jusqu'à ce que je trouve une meilleure solution.

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")

Ensuite, nous trouvons le tout premier cadre de pile du thread principal. En utilisant la fonction spécifique cPython, sys._current_frames() nous obtenons un dictionnaire de la trame de pile actuelle de chaque thread. Ensuite, en utilisant, inspect.getouterframes()nous pouvons récupérer la pile entière pour le thread principal et la toute première image. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Enfin, les différences entre les implémentations Windows et Linux inspect.getouterframes()doivent être gérées. Utilisez le nom de fichier nettoyé os.path.abspath()et os.path.dirname()nettoyez les choses.

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))

Jusqu'à présent, j'ai testé cela sur Python2.7 et 3.6 sur Windows ainsi que sur Python3.4 sur WSL

Joseph Burnitz
la source
0

Si vous travaillez avec anaconda-project, vous pouvez interroger le PROJECT_ROOT à partir de la variable d'environnement -> os.getenv ('PROJECT_ROOT'). Cela ne fonctionne que si le script est exécuté via l'exécution d'un projet anaconda.

Si vous ne voulez pas que votre script soit exécuté par anaconda-project, vous pouvez interroger le chemin absolu du binaire exécutable de l'interpréteur Python que vous utilisez et extraire la chaîne de chemin jusqu'au répertoire envs exclusiv. Par exemple: L'interpréteur python de mon conda env est situé à:

/ home / user / project_root / envs / default / bin / python

# You can first retrieve the env variable PROJECT_DIR.
# If not set, get the python interpreter location and strip off the string till envs inclusiv...

if os.getenv('PROJECT_DIR'):
    PROJECT_DIR = os.getenv('PROJECT_DIR')
else:
    PYTHON_PATH = sys.executable
    path_rem = os.path.join('envs', 'default', 'bin', 'python')
    PROJECT_DIR = py_path.split(path_rem)[0]

Cela ne fonctionne qu'avec conda-project avec une structure de projet fixe d'un anaconda-project

Domsch
la source
0

J'ai utilisé la méthode ../ pour récupérer le chemin actuel du projet.

Exemple: Project1 - D: \ projects

src

Fichiers de configuration

Configuration.cfg

Chemin = "../ src / ConfigurationFiles / Configuration.cfg"

Adarsh
la source
0

Au moment de la rédaction de cet article, aucune des autres solutions n'est vraiment autonome. Ils dépendent soit d'une variable d'environnement, soit de la position du module dans la structure du package. La première réponse avec la solution 'Django' est victime de cette dernière en exigeant une importation relative. Il présente également l'inconvénient de devoir modifier un module au niveau supérieur.

Cela devrait être la bonne approche pour trouver le chemin du répertoire du package de niveau supérieur:

import sys
import os

root_name, _, _ = __name__.partition('.')
root_module = sys.modules[root_name]
root_dir = os.path.dirname(root_module.__file__)

config_path = os.path.join(root_dir, 'configuration.conf')

Il fonctionne en prenant le premier composant de la chaîne en pointillé contenue dans __name__et en l'utilisant comme clé dans sys.moduleslaquelle renvoie l'objet module du package de niveau supérieur. Son __file__attribut contient le chemin que nous voulons après le découpage en /__init__.pyutilisant os.path.dirname().

Cette solution est autonome. Il fonctionne n'importe où dans n'importe quel module du package, y compris dans le __init__.pyfichier de niveau supérieur .

Pyprohly
la source
Pourriez-vous ajouter une brève description de votre solution et comment ils peuvent l'utiliser comme solution?
LuRsT
0

J'ai dû implémenter une solution personnalisée car ce n'est pas aussi simple que vous pourriez le penser. Ma solution est basée sur l'inspection de trace de pile ( inspect.stack()) + sys.pathet fonctionne très bien quel que soit l'emplacement du module python dans lequel la fonction est invoquée ni l'interpréteur (j'ai essayé en l'exécutant dans PyCharm, dans un shell de poésie et autres ... ). Ceci est la mise en œuvre complète avec des commentaires:

def get_project_root_dir() -> str:
    """
    Returns the name of the project root directory.

    :return: Project root directory name
    """

    # stack trace history related to the call of this function
    frame_stack: [FrameInfo] = inspect.stack()

    # get info about the module that has invoked this function
    # (index=0 is always this very module, index=1 is fine as long this function is not called by some other
    # function in this module)
    frame_info: FrameInfo = frame_stack[1]

    # if there are multiple calls in the stacktrace of this very module, we have to skip those and take the first
    # one which comes from another module
    if frame_info.filename == __file__:
        for frame in frame_stack:
            if frame.filename != __file__:
                frame_info = frame
                break

    # path of the module that has invoked this function
    caller_path: str = frame_info.filename

    # absolute path of the of the module that has invoked this function
    caller_absolute_path: str = os.path.abspath(caller_path)

    # get the top most directory path which contains the invoker module
    paths: [str] = [p for p in sys.path if p in caller_absolute_path]
    paths.sort(key=lambda p: len(p))
    caller_root_path: str = paths[0]

    if not os.path.isabs(caller_path):
        # file name of the invoker module (eg: "mymodule.py")
        caller_module_name: str = Path(caller_path).name

        # this piece represents a subpath in the project directory
        # (eg. if the root folder is "myproject" and this function has ben called from myproject/foo/bar/mymodule.py
        # this will be "foo/bar")
        project_related_folders: str = caller_path.replace(os.sep + caller_module_name, '')

        # fix root path by removing the undesired subpath
        caller_root_path = caller_root_path.replace(project_related_folders, '')

    dir_name: str = Path(caller_root_path).name

    return dir_name
daveoncode
la source
-1

Il y a beaucoup de réponses ici mais je n'ai pas trouvé quelque chose de simple qui couvre tous les cas alors permettez-moi de suggérer ma solution aussi:

import pathlib
import os

def get_project_root():
    """
    There is no way in python to get project root. This function uses a trick.
    We know that the function that is currently running is in the project.
    We know that the root project path is in the list of PYTHONPATH
    look for any path in PYTHONPATH list that is contained in this function's path
    Lastly we filter and take the shortest path because we are looking for the root.
    :return: path to project root
    """
    apth = str(pathlib.Path().absolute())
    ppth = os.environ['PYTHONPATH'].split(':')
    matches = [x for x in ppth if x in apth]
    project_root = min(matches, key=len)
    return project_root

alonhzn
la source