├── src └── pyprojroot │ ├── py.typed │ ├── __init__.py │ ├── pyprojroot.py │ ├── here.py │ ├── root.py │ └── criterion.py ├── setup-dev.sh ├── setup.cfg ├── tox.ini ├── .flake8 ├── Makefile ├── .github └── workflows │ ├── python-package-conda.ignore-yml │ └── python-build-test.yml ├── pyproject.toml ├── .devcontainer └── devcontainer.json ├── LICENSE ├── tests └── test_here.py ├── .gitignore └── README.md /src/pyprojroot/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup-dev.sh: -------------------------------------------------------------------------------- 1 | # Script used to set up your current python enviornment 2 | # Primiarly used for the .devcontainer codespace 3 | 4 | 5 | pip install .[dev,test] 6 | -------------------------------------------------------------------------------- /src/pyprojroot/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa: F401 2 | from .criterion import as_root_criterion, has_dir, has_file 3 | from .here import here 4 | from .root import find_root, find_root_with_reason 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [options] 2 | packages = find: 3 | package_dir = 4 | = src 5 | include_package_data = True 6 | 7 | [options.packages.find] 8 | where = src 9 | 10 | [options.package_data] 11 | pyprojroot = 12 | py.typed 13 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | env_list = 3 | py{37,38,39,310,311} 4 | minversion = 4.4.7 5 | 6 | [testenv] 7 | description = run the tests with pytest 8 | package = wheel 9 | wheel_build_env = .pkg 10 | deps = 11 | pytest>=6.2.5 12 | commands = 13 | pytest {tty:--color=yes} {posargs} 14 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore = E203, E266, E501 3 | # line length is intentionally set to 80 here because black uses Bugbear 4 | # See https://github.com/psf/black/blob/master/docs/the_black_code_style.md#line-length for more details 5 | max-line-length = 80 6 | max-complexity = 18 7 | select = B,C,E,F,W,T4,B9 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deploy 2 | deploy: 3 | rm -f dist/* 4 | python3 -m build 5 | python3 -m twine upload dist/* 6 | 7 | .PHONY: test_install 8 | test_install: 9 | python3 -m pip install --index-url https://pypi.org/simple/ --no-deps --upgrade pyprojroot 10 | 11 | .PHONY: lint 12 | lint: 13 | python3 -m mypy --strict src/pyprojroot 14 | python3 -m flake8 src/pyprojroot tests 15 | python3 -m black --check --diff src/pyprojroot tests 16 | 17 | .PHONY: fmt 18 | fmt: 19 | python3 -m black src/pyprojroot tests 20 | 21 | .PHONY: test 22 | test: 23 | PYTHONPATH=src python3 -m pytest 24 | -------------------------------------------------------------------------------- /.github/workflows/python-package-conda.ignore-yml: -------------------------------------------------------------------------------- 1 | name: Python Package using Conda 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-linux: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | max-parallel: 5 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python 3.8 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.8 17 | - name: Install dev dependencies 18 | run: | 19 | $CONDA/bin/conda env update --file environment-dev.yml --name base 20 | - name: Lint 21 | run: | 22 | export PATH="${CONDA}/bin:${PATH}" 23 | make lint 24 | - name: Test with pytest 25 | run: | 26 | export PATH="${CONDA}/bin:${PATH}" 27 | make test 28 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [project] 9 | name = "pyprojroot" 10 | version = "0.3.0" 11 | authors = [ 12 | { name="Daniel Chen", email="chendaniely@gmail.com" }, 13 | ] 14 | description = "Project-oriented workflow in Python" 15 | readme = "README.md" 16 | dependencies = [ 17 | "typing-extensions" 18 | ] 19 | requires-python = ">=3.7" 20 | classifiers = [ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ] 25 | 26 | [project.optional-dependencies] 27 | dev = [ 28 | "black~=19.3b0", 29 | "mypy~=0.720", 30 | "flake8~=3.8.4", 31 | "twine~=4.0.2", 32 | "build~=0.10.0", 33 | ] 34 | test = [ 35 | "pytest>=6.2.5", 36 | ] 37 | 38 | [project.urls] 39 | "Homepage" = "https://github.com/chendaniely/pyprojroot" 40 | "Bug Tracker" = "https://github.com/chendaniely/pyprojroot/issues" -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/python 3 | { 4 | "name": "Python 3", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/python:0-3.7", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | // "postCreateCommand": "pip3 install --user -r requirements.txt", 16 | "postCreateCommand": "bash setup-dev.sh" 17 | 18 | // Configure tool-specific properties. 19 | // "customizations": {}, 20 | 21 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 22 | // "remoteUser": "root" 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Daniel Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/python-build-test.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v4 21 | 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install flake8 pytest mypy black 28 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 29 | - name: Lint with flake8 30 | run: | 31 | flake8 src/pyprojroot tests 32 | - name: MyPy (type) checking 33 | run: | 34 | mypy --strict src/pyprojroot 35 | - name: Format with black 36 | run: | 37 | black --check --diff src/pyprojroot tests 38 | - name: Test with pytest 39 | run: | 40 | PYTHONPATH=src pytest 41 | -------------------------------------------------------------------------------- /tests/test_here.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from pyprojroot.here import here 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "project_files,file_type", 10 | [ 11 | (".git", "dir"), 12 | (".here", "file"), 13 | ("my_project.Rproj", "file"), 14 | ("requirements.txt", "file"), 15 | ("setup.py", "file"), 16 | (".dvc", "dir"), 17 | ], 18 | ) 19 | @pytest.mark.parametrize("child_dir", ["stuff", "src", "data", "data/hello"]) 20 | def test_here(tmp_path, project_files, file_type, child_dir): 21 | """ 22 | This test uses pytest's tmp_path facilities to create a simulated project 23 | directory, and checks that the path is correct. 24 | """ 25 | # Create project file 26 | if file_type == "file": 27 | (tmp_path / project_files).write_text("blah") 28 | elif file_type == "dir": 29 | (tmp_path / project_files).mkdir(parents=True) 30 | else: 31 | raise ValueError(f"Invalid input: {file_type}") 32 | 33 | # Create child dirs 34 | start_dir = tmp_path / child_dir 35 | start_dir.mkdir(parents=True) 36 | os.chdir(start_dir) 37 | 38 | # Verify the project against current work directory 39 | current_path = here() 40 | assert current_path == tmp_path 41 | -------------------------------------------------------------------------------- /src/pyprojroot/pyprojroot.py: -------------------------------------------------------------------------------- 1 | """DEPRECATED 2 | 3 | This is explicitly providing the 0.2.0 version's interface of pyprojroot 4 | and marked deprecated 5 | """ 6 | 7 | import warnings 8 | from pathlib import Path 9 | from typing import Optional, Tuple 10 | 11 | from .criterion import _PathType, as_root_criterion 12 | from .here import CRITERIA 13 | from .root import find_root_with_reason 14 | 15 | 16 | def py_project_root(path: _PathType, project_files: Tuple[str, ...]) -> Path: 17 | criteria = [as_root_criterion(project_file) for project_file in project_files] 18 | root, _ = find_root_with_reason(criteria, path) 19 | return root 20 | 21 | 22 | def here( 23 | relative_project_path: _PathType = "", 24 | project_files: Optional[Tuple[str, ...]] = None, 25 | warn_missing: bool = False, 26 | ) -> Path: 27 | if project_files is None: 28 | path, _ = find_root_with_reason(criterion=CRITERIA, start=".") 29 | else: 30 | path = py_project_root(path=".", project_files=project_files) 31 | 32 | if relative_project_path: 33 | path = path / relative_project_path 34 | 35 | if warn_missing and not path.exists(): 36 | warnings.warn(f"Path doesn't exist: {path!s}") 37 | return path 38 | 39 | 40 | __all__ = ["here", "py_project_root"] 41 | 42 | warnings.warn( 43 | "Importing deprecated module `pyprojroot.pyprojroot`.", 44 | DeprecationWarning, 45 | ) 46 | -------------------------------------------------------------------------------- /src/pyprojroot/here.py: -------------------------------------------------------------------------------- 1 | """Convenience to find the path for a file or directory relative to the root. 2 | 3 | This module is inspired by the `here` library for R. 4 | See https://github.com/r-lib/here. 5 | 6 | It is intended for interactive use only. 7 | """ 8 | 9 | import warnings 10 | from pathlib import Path 11 | from typing import Tuple 12 | 13 | from . import criterion 14 | from .root import find_root_with_reason 15 | 16 | 17 | CRITERIA = [ 18 | criterion.has_file(".here"), 19 | criterion.has_dir(".git"), 20 | criterion.matches_glob("*.Rproj"), 21 | criterion.has_file("requirements.txt"), 22 | criterion.has_file("setup.py"), 23 | criterion.has_dir(".dvc"), 24 | criterion.has_dir(".spyproject"), 25 | criterion.has_file("pyproject.toml"), 26 | criterion.has_dir(".idea"), 27 | criterion.has_dir(".vscode"), 28 | ] 29 | 30 | 31 | def get_here() -> Tuple[Path, str]: 32 | """Return a tuple with the root path and a reason""" 33 | start = Path.cwd() 34 | path, reason = find_root_with_reason(CRITERIA, start=start) 35 | return path, reason 36 | 37 | 38 | # TODO: Implement set_here 39 | 40 | 41 | def here( 42 | relative_project_path: criterion._PathType = "", warn_missing: bool = False 43 | ) -> Path: 44 | """ 45 | Returns the path relative to the projects root directory. 46 | 47 | :param relative_project_path: relative path from project root 48 | :param warn_missing: warn user if path does not exist (default=False) 49 | :return: pathlib path 50 | 51 | Note: `reason` is not yet implemented. 52 | """ 53 | path, reason = get_here() 54 | 55 | if relative_project_path: 56 | path = path / relative_project_path 57 | 58 | if warn_missing and not path.exists(): 59 | warnings.warn(f"Path doesn't exist: {path!s}") 60 | return path 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | .vscode/* 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | env/ 91 | venv/ 92 | ENV/ 93 | env.bak/ 94 | venv.bak/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | 109 | 110 | # PyCharm 111 | .idea/* -------------------------------------------------------------------------------- /src/pyprojroot/root.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is inspired by the `rprojroot` library for R. 3 | See https://github.com/r-lib/rprojroot. 4 | 5 | It is intended for interactive or programmatic only. 6 | 7 | NOTE: `reason` is not fully implemented. 8 | """ 9 | 10 | from pathlib import Path 11 | from typing import Union, Tuple 12 | 13 | from .criterion import ( 14 | as_root_criterion as _as_root_criterion, 15 | _CriterionType, 16 | _PathType, 17 | ) 18 | 19 | 20 | def as_start_path(start: Union[None, _PathType]) -> Path: 21 | if start is None: 22 | return Path.cwd() 23 | if not isinstance(start, Path): 24 | start = Path(start) 25 | # TODO: consider `start = start.resolve()` 26 | return start 27 | 28 | 29 | def find_root_with_reason( 30 | criterion: _CriterionType, 31 | start: Union[None, _PathType] = None, 32 | ) -> Tuple[Path, str]: 33 | """ 34 | Find directory matching root criterion with reason. 35 | 36 | Recursively search parents of start path for directory 37 | matching root criterion with reason. 38 | 39 | NOTE: `reason` is not fully implemented. 40 | """ 41 | 42 | # Prepare inputs 43 | criterion = _as_root_criterion(criterion) 44 | start = as_start_path(start) 45 | 46 | # Check start 47 | if start.is_dir() and criterion(start): 48 | return start, "Pass" 49 | 50 | # Iterate over all parents 51 | for p in start.parents: 52 | if criterion(p): 53 | return p, "Pass" 54 | 55 | raise RuntimeError("Project root not found.") 56 | 57 | 58 | def find_root( 59 | criterion: _CriterionType, 60 | start: Union[None, _PathType] = None, 61 | ) -> Path: 62 | """ 63 | Find directory matching root criterion. 64 | 65 | Recursively search parents of start path for directory 66 | matching root criterion. 67 | """ 68 | try: 69 | root, _ = find_root_with_reason(criterion, start=start) 70 | except RuntimeError as ex: 71 | raise ex 72 | else: 73 | return root 74 | -------------------------------------------------------------------------------- /src/pyprojroot/criterion.py: -------------------------------------------------------------------------------- 1 | """Set and use criteria to find the project root. 2 | 3 | This module is inspired by the `rprojroot` library for R. 4 | See https://github.com/r-lib/rprojroot. 5 | 6 | It is intended for interactive or programmatic only. 7 | """ 8 | 9 | from pathlib import Path 10 | from typing import Callable, Iterable, Union 11 | from os import PathLike as _PathLike 12 | 13 | 14 | _PathType = Union[_PathLike, str] 15 | _CriterionType = Union[ 16 | Callable[[_PathType], bool], 17 | Callable[[Path], bool], 18 | _PathType, 19 | Path, 20 | Iterable[Callable[[_PathType], bool]], 21 | Iterable[Callable[[Path], bool]], 22 | ] 23 | 24 | 25 | def as_root_criterion( 26 | criterion: _CriterionType, 27 | ) -> Callable[[Path], bool]: 28 | if callable(criterion): 29 | return criterion 30 | 31 | # criterion must be a Collection, rather than just Iterable 32 | if isinstance(criterion, (_PathLike, str)): 33 | criterion_collection = [criterion] 34 | else: 35 | criterion_collection = list(criterion) # type: ignore[arg-type] 36 | 37 | def f(path: Path) -> bool: 38 | for c in criterion_collection: 39 | if isinstance(c, (_PathLike, str)): 40 | if (path / c).exists(): 41 | return True 42 | else: 43 | if c(path): 44 | return True 45 | return False 46 | 47 | return f 48 | 49 | 50 | def has_file(file: _PathType) -> Callable[[Path], bool]: 51 | """ 52 | Check that specified file exists in path. 53 | 54 | Note that a directory with that name will not match. 55 | """ 56 | 57 | def f(path: Path) -> bool: 58 | return (path / file).is_file() 59 | 60 | return f 61 | 62 | 63 | def has_dir(file: _PathType) -> Callable[[Path], bool]: 64 | """ 65 | Check that specified directory exists. 66 | 67 | Note that a regular file with that name will not match. 68 | """ 69 | 70 | def f(path: Path) -> bool: 71 | return (path / file).is_dir() 72 | 73 | return f 74 | 75 | 76 | def matches_glob(pat: str) -> Callable[[Path], bool]: 77 | """ 78 | Check that glob has at least one match. 79 | """ 80 | 81 | def f(path: Path) -> bool: 82 | matches = path.glob(pat) 83 | try: 84 | # Only need to get one item from generator 85 | next(matches) 86 | except StopIteration: 87 | return False 88 | else: 89 | return True 90 | 91 | return f 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project-oriented workflow in Python 2 | 3 | Finding project directories in Python (data science) projects. 4 | 5 | This library aims to provide both 6 | the programmatic functionality from the R [`rprojroot`][rprojroot] package 7 | and the interactive functionality from the R [`here`][here] package. 8 | 9 | ## Motivation 10 | 11 | **Problem**: I have a project that has a specific folder structure, 12 | for example, one mentioned in [Noble 2009][noble2009] or something similar to [this project template][project-template], 13 | and I want to be able to: 14 | 15 | 1. Run my python scripts without having to specify a series of `../` to get to the `data` folder. 16 | 2. `cd` into the directory of my python script instead of calling it from the root project directory and specify all the folders to the script. 17 | 3. Reference datasets from a root directory when using a jupyter notebook because everytime I use a jupyter notebook, 18 | the working directory changes to the location of the notebook, not where I launched the notebook server. 19 | 20 | **Solution**: `pyprojroot` finds the root working directory for your project as a `pathlib.Path` object. 21 | You can now use the `here` function to pass in a relative path from the project root directory 22 | (no matter what working directory you are in the project), 23 | and you will get a full path to the specified file. 24 | That is, in a jupyter notebook, 25 | you can write something like `pandas.read_csv(here('data/my_data.csv'))` 26 | instead of `pandas.read_csv('../data/my_data.csv')`. 27 | This allows you to restructure the files in your project without having to worry about changing file paths. 28 | 29 | Great for reading and writing datasets! 30 | 31 | Further reading: 32 | 33 | * [Project-oriented workflows](https://www.tidyverse.org/articles/2017/12/workflow-vs-script/) 34 | * [Stop the working directory insanity](https://gist.github.com/jennybc/362f52446fe1ebc4c49f) 35 | * [Ode to the here package](https://github.com/jennybc/here_here) 36 | 37 | ## Installation 38 | 39 | ### pip 40 | 41 | ```bash 42 | python -m pip install pyprojroot 43 | ``` 44 | 45 | ### conda 46 | 47 | https://anaconda.org/conda-forge/pyprojroot 48 | 49 | ```bash 50 | conda install -c conda-forge pyprojroot 51 | ``` 52 | 53 | ## Example Usage 54 | 55 | `pyprojroot` looks for certain files like `.here` or `.git` to identify the `here` directory. To make any of the following examples work, you'll need one of those files in the current directory or one of its parents. (For the complete list of files, see [here.py](src/pyprojroot/here.py).) 56 | 57 | ### Interactive 58 | 59 | This is based on the R [`here`][here] library. 60 | 61 | ```python 62 | from pyprojroot.here import here 63 | 64 | here() 65 | ``` 66 | 67 | ### Programmatic 68 | 69 | This based on the R [`rprojroot`][rprojroot] library. 70 | 71 | ```python 72 | import pyprojroot 73 | 74 | base_path = pyprojroot.find_root(pyprojroot.has_dir(".git")) 75 | ``` 76 | 77 | ## Demonstration 78 | 79 | Load the packages 80 | 81 | ``` 82 | In [1]: from pyprojroot.here import here 83 | In [2]: import pandas as pd 84 | ``` 85 | 86 | The current working directory is the "notebooks" folder 87 | 88 | ``` 89 | In [3]: !pwd 90 | /home/dchen/git/hub/scipy-2019-pandas/notebooks 91 | ``` 92 | 93 | In the notebooks folder, I have all my notebooks 94 | 95 | ``` 96 | In [4]: !ls 97 | 01-intro.ipynb 02-tidy.ipynb 03-apply.ipynb 04-plots.ipynb 05-model.ipynb Untitled.ipynb 98 | ``` 99 | 100 | If I wanted to access data in my notebooks I'd have to use `../data` 101 | 102 | ``` 103 | In [5]: !ls ../data 104 | billboard.csv country_timeseries.csv gapminder.tsv pew.csv table1.csv table2.csv table3.csv table4a.csv table4b.csv weather.csv 105 | ``` 106 | 107 | However, with there `here` function, I can access my data all from the project root. 108 | This means if I move the notebook to another folder or subfolder I don't have to change the path to my data. 109 | Only if I move the data to another folder would I need to change the path in my notebook (or script) 110 | 111 | ``` 112 | In [6]: pd.read_csv(here('data/gapminder.tsv'), sep='\t').head() 113 | Out[6]: 114 | country continent year lifeExp pop gdpPercap 115 | 0 Afghanistan Asia 1952 28.801 8425333 779.445314 116 | 1 Afghanistan Asia 1957 30.332 9240934 820.853030 117 | 2 Afghanistan Asia 1962 31.997 10267083 853.100710 118 | 3 Afghanistan Asia 1967 34.020 11537966 836.197138 119 | 4 Afghanistan Asia 1972 36.088 13079460 739.981106 120 | ``` 121 | 122 | By the way, you get a `pathlib.Path` object path back! 123 | 124 | ``` 125 | In [7]: here('data/gapminder.tsv') 126 | Out[7]: PosixPath('/home/dchen/git/hub/scipy-2019-pandas/data/gapminder.tsv') 127 | ``` 128 | 129 | [here]: https://github.com/r-lib/here 130 | [rprojroot]: https://github.com/r-lib/rprojroot 131 | [noble2009]: https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1000424 132 | [project-template]: https://chendaniely.github.io/sdal/2017/05/30/project_templates/ 133 | --------------------------------------------------------------------------------