pyprojroot-0.3.0/0000755000175000017500000000000014403527215013272 5ustar dandan00000000000000pyprojroot-0.3.0/LICENSE0000644000175000017500000000205414403244516014300 0ustar dandan00000000000000MIT License Copyright (c) 2019 Daniel Chen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyprojroot-0.3.0/PKG-INFO0000644000175000017500000001115514403527215014372 0ustar dandan00000000000000Metadata-Version: 2.1 Name: pyprojroot Version: 0.3.0 Summary: Project-oriented workflow in Python Author-email: Daniel Chen Project-URL: Homepage, https://github.com/chendaniely/pyprojroot Project-URL: Bug Tracker, https://github.com/chendaniely/pyprojroot/issues Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE # Project-oriented workflow in Python Finding project directories in Python (data science) projects. This library aims to provide both the programmatic functionality from the R [`rprojroot`][rprojroot] package and the interactive functionality from the R [`here`][here] package. ## Motivation **Problem**: I have a project that has a specific folder structure, for example, one mentioned in [Noble 2009][noble2009] or something similar to [this project template][project-template], and I want to be able to: 1. Run my python scripts without having to specify a series of `../` to get to the `data` folder. 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. 3. Reference datasets from a root directory when using a jupyter notebook because everytime I use a jupyter notebook, the working directory changes to the location of the notebook, not where I launched the notebook server. **Solution**: `pyprojroot` finds the root working directory for your project as a `pathlib.Path` object. You can now use the `here` function to pass in a relative path from the project root directory (no matter what working directory you are in the project), and you will get a full path to the specified file. That is, in a jupyter notebook, you can write something like `pandas.read_csv(here('data/my_data.csv'))` instead of `pandas.read_csv('../data/my_data.csv')`. This allows you to restructure the files in your project without having to worry about changing file paths. Great for reading and writing datasets! Further reading: * [Project-oriented workflows](https://www.tidyverse.org/articles/2017/12/workflow-vs-script/) * [Stop the working directory insanity](https://gist.github.com/jennybc/362f52446fe1ebc4c49f) * [Ode to the here package](https://github.com/jennybc/here_here) ## Installation ### pip ```bash python -m pip install pyprojroot ``` ### conda https://anaconda.org/conda-forge/pyprojroot ```bash conda install -c conda-forge pyprojroot ``` ## Example Usage ### Interactive This is based on the R [`here`][here] library. ```python from pyprojroot.here import here here() ``` ### Programmatic This based on the R [`rprojroot`][rprojroot] library. ```python import pyprojroot base_path = pyprojroot.find_root(pyprojroot.has_dir(".git")) ``` ## Demonstration Load the packages ``` In [1]: from pyprojroot.here import here In [2]: import pandas as pd ``` The current working directory is the "notebooks" folder ``` In [3]: !pwd /home/dchen/git/hub/scipy-2019-pandas/notebooks ``` In the notebooks folder, I have all my notebooks ``` In [4]: !ls 01-intro.ipynb 02-tidy.ipynb 03-apply.ipynb 04-plots.ipynb 05-model.ipynb Untitled.ipynb ``` If I wanted to access data in my notebooks I'd have to use `../data` ``` In [5]: !ls ../data billboard.csv country_timeseries.csv gapminder.tsv pew.csv table1.csv table2.csv table3.csv table4a.csv table4b.csv weather.csv ``` However, with there `here` function, I can access my data all from the project root. This means if I move the notebook to another folder or subfolder I don't have to change the path to my data. Only if I move the data to another folder would I need to change the path in my notebook (or script) ``` In [6]: pd.read_csv(here('data/gapminder.tsv'), sep='\t').head() Out[6]: country continent year lifeExp pop gdpPercap 0 Afghanistan Asia 1952 28.801 8425333 779.445314 1 Afghanistan Asia 1957 30.332 9240934 820.853030 2 Afghanistan Asia 1962 31.997 10267083 853.100710 3 Afghanistan Asia 1967 34.020 11537966 836.197138 4 Afghanistan Asia 1972 36.088 13079460 739.981106 ``` By the way, you get a `pathlib.Path` object path back! ``` In [7]: here('data/gapminder.tsv') Out[7]: PosixPath('/home/dchen/git/hub/scipy-2019-pandas/data/gapminder.tsv') ``` [here]: https://github.com/r-lib/here [rprojroot]: https://github.com/r-lib/rprojroot [noble2009]: https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1000424 [project-template]: https://chendaniely.github.io/sdal/2017/05/30/project_templates/ pyprojroot-0.3.0/README.md0000644000175000017500000001014414403522121014540 0ustar dandan00000000000000# Project-oriented workflow in Python Finding project directories in Python (data science) projects. This library aims to provide both the programmatic functionality from the R [`rprojroot`][rprojroot] package and the interactive functionality from the R [`here`][here] package. ## Motivation **Problem**: I have a project that has a specific folder structure, for example, one mentioned in [Noble 2009][noble2009] or something similar to [this project template][project-template], and I want to be able to: 1. Run my python scripts without having to specify a series of `../` to get to the `data` folder. 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. 3. Reference datasets from a root directory when using a jupyter notebook because everytime I use a jupyter notebook, the working directory changes to the location of the notebook, not where I launched the notebook server. **Solution**: `pyprojroot` finds the root working directory for your project as a `pathlib.Path` object. You can now use the `here` function to pass in a relative path from the project root directory (no matter what working directory you are in the project), and you will get a full path to the specified file. That is, in a jupyter notebook, you can write something like `pandas.read_csv(here('data/my_data.csv'))` instead of `pandas.read_csv('../data/my_data.csv')`. This allows you to restructure the files in your project without having to worry about changing file paths. Great for reading and writing datasets! Further reading: * [Project-oriented workflows](https://www.tidyverse.org/articles/2017/12/workflow-vs-script/) * [Stop the working directory insanity](https://gist.github.com/jennybc/362f52446fe1ebc4c49f) * [Ode to the here package](https://github.com/jennybc/here_here) ## Installation ### pip ```bash python -m pip install pyprojroot ``` ### conda https://anaconda.org/conda-forge/pyprojroot ```bash conda install -c conda-forge pyprojroot ``` ## Example Usage ### Interactive This is based on the R [`here`][here] library. ```python from pyprojroot.here import here here() ``` ### Programmatic This based on the R [`rprojroot`][rprojroot] library. ```python import pyprojroot base_path = pyprojroot.find_root(pyprojroot.has_dir(".git")) ``` ## Demonstration Load the packages ``` In [1]: from pyprojroot.here import here In [2]: import pandas as pd ``` The current working directory is the "notebooks" folder ``` In [3]: !pwd /home/dchen/git/hub/scipy-2019-pandas/notebooks ``` In the notebooks folder, I have all my notebooks ``` In [4]: !ls 01-intro.ipynb 02-tidy.ipynb 03-apply.ipynb 04-plots.ipynb 05-model.ipynb Untitled.ipynb ``` If I wanted to access data in my notebooks I'd have to use `../data` ``` In [5]: !ls ../data billboard.csv country_timeseries.csv gapminder.tsv pew.csv table1.csv table2.csv table3.csv table4a.csv table4b.csv weather.csv ``` However, with there `here` function, I can access my data all from the project root. This means if I move the notebook to another folder or subfolder I don't have to change the path to my data. Only if I move the data to another folder would I need to change the path in my notebook (or script) ``` In [6]: pd.read_csv(here('data/gapminder.tsv'), sep='\t').head() Out[6]: country continent year lifeExp pop gdpPercap 0 Afghanistan Asia 1952 28.801 8425333 779.445314 1 Afghanistan Asia 1957 30.332 9240934 820.853030 2 Afghanistan Asia 1962 31.997 10267083 853.100710 3 Afghanistan Asia 1967 34.020 11537966 836.197138 4 Afghanistan Asia 1972 36.088 13079460 739.981106 ``` By the way, you get a `pathlib.Path` object path back! ``` In [7]: here('data/gapminder.tsv') Out[7]: PosixPath('/home/dchen/git/hub/scipy-2019-pandas/data/gapminder.tsv') ``` [here]: https://github.com/r-lib/here [rprojroot]: https://github.com/r-lib/rprojroot [noble2009]: https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1000424 [project-template]: https://chendaniely.github.io/sdal/2017/05/30/project_templates/ pyprojroot-0.3.0/pyproject.toml0000644000175000017500000000121414403522121016173 0ustar dandan00000000000000[build-system] requires = [ "setuptools>=42", "wheel" ] build-backend = "setuptools.build_meta" [project] name = "pyprojroot" version = "0.3.0" authors = [ { name="Daniel Chen", email="chendaniely@gmail.com" }, ] description = "Project-oriented workflow in Python" readme = "README.md" dependencies = [ "typing-extensions" ] requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] [project.urls] "Homepage" = "https://github.com/chendaniely/pyprojroot" "Bug Tracker" = "https://github.com/chendaniely/pyprojroot/issues"pyprojroot-0.3.0/setup.cfg0000644000175000017500000000031114403527215015106 0ustar dandan00000000000000[options] packages = find: package_dir = = src include_package_data = True [options.packages.find] where = src [options.package_data] pyprojroot = py.typed [egg_info] tag_build = tag_date = 0 pyprojroot-0.3.0/src/0000755000175000017500000000000014403527215014061 5ustar dandan00000000000000pyprojroot-0.3.0/src/pyprojroot/0000755000175000017500000000000014403527215016310 5ustar dandan00000000000000pyprojroot-0.3.0/src/pyprojroot/__init__.py0000644000175000017500000000040714403522121020411 0ustar dandan00000000000000from .criterion import as_root_criterion, has_dir, has_file from .root import find_root, find_root_with_reason from .here import here __all__ = [ "as_root_criterion", "find_root_with_reason", "find_root", "has_dir", "has_file", "here", ] pyprojroot-0.3.0/src/pyprojroot/criterion.py0000644000175000017500000000460214403522121020651 0ustar dandan00000000000000""" This module is inspired by the `rprojroot` library for R. See https://github.com/r-lib/rprojroot. It is intended for interactive or programmatic only. """ import pathlib as _pathlib import typing from os import PathLike as _PathLike _PathType = typing.Union[_PathLike, str] _CriterionType = typing.Union[ typing.Callable[[_PathType], bool], typing.Callable[[_pathlib.Path], bool], _PathType, _pathlib.Path, typing.Iterable[typing.Callable[[_PathType], bool]], typing.Iterable[typing.Callable[[_pathlib.Path], bool]], ] # TODO: It would be nice to have a class that encapsulates these checks, # so that we can implement methods like |, !, &, ^ operators # TODO: Refactor in a way that allows creation of reasons def as_root_criterion( criterion: _CriterionType, ) -> typing.Callable[[_pathlib.Path], bool]: if callable(criterion): return criterion # criterion must be a Collection, rather than just Iterable if isinstance(criterion, (_PathLike, str)): criterion_collection = [criterion] else: criterion_collection = list(criterion) # type: ignore[arg-type] def f(path: _pathlib.Path) -> bool: for c in criterion_collection: if isinstance(c, (_PathLike, str)): if (path / c).exists(): return True else: if c(path): return True return False return f def has_file(file: _PathType) -> typing.Callable[[_pathlib.Path], bool]: """ Check that specified file exists in path. Note that a directory with that name will not match. """ def f(path: _pathlib.Path) -> bool: return (path / file).is_file() return f def has_dir(file: _PathType) -> typing.Callable[[_pathlib.Path], bool]: """ Check that specified directory exists. Note that a regular file with that name will not match. """ def f(path: _pathlib.Path) -> bool: return (path / file).is_dir() return f def matches_glob(pat: str) -> typing.Callable[[_pathlib.Path], bool]: """ Check that glob has at least one match. """ def f(path: _pathlib.Path) -> bool: matches = path.glob(pat) try: # Only need to get one item from generator next(matches) except StopIteration: return False else: return True return f pyprojroot-0.3.0/src/pyprojroot/here.py0000644000175000017500000000276614403522121017607 0ustar dandan00000000000000""" This module is inspired by the `here` library for R. See https://github.com/r-lib/here. It is intended for interactive use only. """ import pathlib as _pathlib import warnings as _warnings import typing from . import criterion from .root import find_root_with_reason CRITERIA = [ criterion.has_file(".here"), criterion.has_dir(".git"), criterion.matches_glob("*.Rproj"), criterion.has_file("requirements.txt"), criterion.has_file("setup.py"), criterion.has_dir(".dvc"), criterion.has_dir(".spyproject"), criterion.has_file("pyproject.toml"), criterion.has_dir(".idea"), criterion.has_dir(".vscode"), ] def get_here() -> typing.Tuple[_pathlib.Path, str]: # TODO: This should only find_root once per session start = _pathlib.Path.cwd() path, reason = find_root_with_reason(CRITERIA, start=start) return path, reason # TODO: Implement set_here def here( relative_project_path: criterion._PathType = "", warn_missing: bool = False ) -> _pathlib.Path: """ Returns the path relative to the projects root directory. :param relative_project_path: relative path from project root :param warn_missing: warn user if path does not exist (default=False) :return: pathlib path """ path, reason = get_here() # TODO: Show reason when requested if relative_project_path: path = path / relative_project_path if warn_missing and not path.exists(): _warnings.warn(f"Path doesn't exist: {path!s}") return path pyprojroot-0.3.0/src/pyprojroot/py.typed0000644000175000017500000000000014403522121017764 0ustar dandan00000000000000pyprojroot-0.3.0/src/pyprojroot/pyprojroot.py0000644000175000017500000000224714403522121021105 0ustar dandan00000000000000# this is explicitly providing the 0.2.0 version's interface of pyprojroot # and marked deprecated import warnings from pathlib import Path from typing import Optional, Tuple from .criterion import _PathType, as_root_criterion from .here import CRITERIA from .root import find_root_with_reason def py_project_root(path: _PathType, project_files: Tuple[str, ...]) -> Path: criteria = [as_root_criterion(project_file) for project_file in project_files] root, _ = find_root_with_reason(criteria, path) return root def here( relative_project_path: _PathType = "", project_files: Optional[Tuple[str, ...]] = None, warn_missing: bool = False, ) -> Path: if project_files is None: path, _ = find_root_with_reason(criterion=CRITERIA, start=".") else: path = py_project_root(path=".", project_files=project_files) if relative_project_path: path = path / relative_project_path if warn_missing and not path.exists(): warnings.warn(f"Path doesn't exist: {path!s}") return path __all__ = ["here", "py_project_root"] warnings.warn( "Importing deprecated module `pyprojroot.pyprojroot`.", DeprecationWarning, ) pyprojroot-0.3.0/src/pyprojroot/root.py0000644000175000017500000000350514403522121017637 0ustar dandan00000000000000""" This module is inspired by the `rprojroot` library for R. See https://github.com/r-lib/rprojroot. It is intended for interactive or programmatic only. """ import pathlib as _pathlib import typing as _typing from .criterion import ( as_root_criterion as _as_root_criterion, _CriterionType, _PathType, ) def as_start_path(start: _typing.Union[None, _PathType]) -> _pathlib.Path: if start is None: return _pathlib.Path.cwd() if not isinstance(start, _pathlib.Path): start = _pathlib.Path(start) # TODO: consider `start = start.resolve()` return start def find_root_with_reason( criterion: _CriterionType, start: _typing.Union[None, _PathType] = None, ) -> _typing.Tuple[_pathlib.Path, str]: """ Find directory matching root criterion with reason. Recursively search parents of start path for directory matching root criterion with reason. """ # TODO: Implement reasons # Prepare inputs criterion = _as_root_criterion(criterion) start = as_start_path(start) # Check start if start.is_dir() and criterion(start): return start, "Pass" # Iterate over all parents # TODO: Consider adding maximum depth # TODO: Consider limiting depth to path (e.g. "if p == stop: raise") for p in start.parents: if criterion(p): return p, "Pass" # Not found raise RuntimeError("Project root not found.") def find_root( criterion: _CriterionType, start: _typing.Union[None, _PathType] = None, ) -> _pathlib.Path: """ Find directory matching root criterion. Recursively search parents of start path for directory matching root criterion. """ try: root, _ = find_root_with_reason(criterion, start=start) except RuntimeError as ex: raise ex else: return root pyprojroot-0.3.0/src/pyprojroot.egg-info/0000755000175000017500000000000014403527215020002 5ustar dandan00000000000000pyprojroot-0.3.0/src/pyprojroot.egg-info/PKG-INFO0000644000175000017500000001115514403527215021102 0ustar dandan00000000000000Metadata-Version: 2.1 Name: pyprojroot Version: 0.3.0 Summary: Project-oriented workflow in Python Author-email: Daniel Chen Project-URL: Homepage, https://github.com/chendaniely/pyprojroot Project-URL: Bug Tracker, https://github.com/chendaniely/pyprojroot/issues Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE # Project-oriented workflow in Python Finding project directories in Python (data science) projects. This library aims to provide both the programmatic functionality from the R [`rprojroot`][rprojroot] package and the interactive functionality from the R [`here`][here] package. ## Motivation **Problem**: I have a project that has a specific folder structure, for example, one mentioned in [Noble 2009][noble2009] or something similar to [this project template][project-template], and I want to be able to: 1. Run my python scripts without having to specify a series of `../` to get to the `data` folder. 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. 3. Reference datasets from a root directory when using a jupyter notebook because everytime I use a jupyter notebook, the working directory changes to the location of the notebook, not where I launched the notebook server. **Solution**: `pyprojroot` finds the root working directory for your project as a `pathlib.Path` object. You can now use the `here` function to pass in a relative path from the project root directory (no matter what working directory you are in the project), and you will get a full path to the specified file. That is, in a jupyter notebook, you can write something like `pandas.read_csv(here('data/my_data.csv'))` instead of `pandas.read_csv('../data/my_data.csv')`. This allows you to restructure the files in your project without having to worry about changing file paths. Great for reading and writing datasets! Further reading: * [Project-oriented workflows](https://www.tidyverse.org/articles/2017/12/workflow-vs-script/) * [Stop the working directory insanity](https://gist.github.com/jennybc/362f52446fe1ebc4c49f) * [Ode to the here package](https://github.com/jennybc/here_here) ## Installation ### pip ```bash python -m pip install pyprojroot ``` ### conda https://anaconda.org/conda-forge/pyprojroot ```bash conda install -c conda-forge pyprojroot ``` ## Example Usage ### Interactive This is based on the R [`here`][here] library. ```python from pyprojroot.here import here here() ``` ### Programmatic This based on the R [`rprojroot`][rprojroot] library. ```python import pyprojroot base_path = pyprojroot.find_root(pyprojroot.has_dir(".git")) ``` ## Demonstration Load the packages ``` In [1]: from pyprojroot.here import here In [2]: import pandas as pd ``` The current working directory is the "notebooks" folder ``` In [3]: !pwd /home/dchen/git/hub/scipy-2019-pandas/notebooks ``` In the notebooks folder, I have all my notebooks ``` In [4]: !ls 01-intro.ipynb 02-tidy.ipynb 03-apply.ipynb 04-plots.ipynb 05-model.ipynb Untitled.ipynb ``` If I wanted to access data in my notebooks I'd have to use `../data` ``` In [5]: !ls ../data billboard.csv country_timeseries.csv gapminder.tsv pew.csv table1.csv table2.csv table3.csv table4a.csv table4b.csv weather.csv ``` However, with there `here` function, I can access my data all from the project root. This means if I move the notebook to another folder or subfolder I don't have to change the path to my data. Only if I move the data to another folder would I need to change the path in my notebook (or script) ``` In [6]: pd.read_csv(here('data/gapminder.tsv'), sep='\t').head() Out[6]: country continent year lifeExp pop gdpPercap 0 Afghanistan Asia 1952 28.801 8425333 779.445314 1 Afghanistan Asia 1957 30.332 9240934 820.853030 2 Afghanistan Asia 1962 31.997 10267083 853.100710 3 Afghanistan Asia 1967 34.020 11537966 836.197138 4 Afghanistan Asia 1972 36.088 13079460 739.981106 ``` By the way, you get a `pathlib.Path` object path back! ``` In [7]: here('data/gapminder.tsv') Out[7]: PosixPath('/home/dchen/git/hub/scipy-2019-pandas/data/gapminder.tsv') ``` [here]: https://github.com/r-lib/here [rprojroot]: https://github.com/r-lib/rprojroot [noble2009]: https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1000424 [project-template]: https://chendaniely.github.io/sdal/2017/05/30/project_templates/ pyprojroot-0.3.0/src/pyprojroot.egg-info/SOURCES.txt0000644000175000017500000000062414403527215021670 0ustar dandan00000000000000LICENSE README.md pyproject.toml setup.cfg src/pyprojroot/__init__.py src/pyprojroot/criterion.py src/pyprojroot/here.py src/pyprojroot/py.typed src/pyprojroot/pyprojroot.py src/pyprojroot/root.py src/pyprojroot.egg-info/PKG-INFO src/pyprojroot.egg-info/SOURCES.txt src/pyprojroot.egg-info/dependency_links.txt src/pyprojroot.egg-info/requires.txt src/pyprojroot.egg-info/top_level.txt tests/test_here.pypyprojroot-0.3.0/src/pyprojroot.egg-info/dependency_links.txt0000644000175000017500000000000114403527215024050 0ustar dandan00000000000000 pyprojroot-0.3.0/src/pyprojroot.egg-info/requires.txt0000644000175000017500000000002214403527215022374 0ustar dandan00000000000000typing-extensions pyprojroot-0.3.0/src/pyprojroot.egg-info/top_level.txt0000644000175000017500000000001314403527215022526 0ustar dandan00000000000000pyprojroot pyprojroot-0.3.0/tests/0000755000175000017500000000000014403527215014434 5ustar dandan00000000000000pyprojroot-0.3.0/tests/test_here.py0000644000175000017500000000211214403522121016753 0ustar dandan00000000000000import os import pytest from pyprojroot.here import here @pytest.mark.parametrize( "project_files,file_type", [ (".git", "dir"), (".here", "file"), ("my_project.Rproj", "file"), ("requirements.txt", "file"), ("setup.py", "file"), (".dvc", "dir"), ], ) @pytest.mark.parametrize("child_dir", ["stuff", "src", "data", "data/hello"]) def test_here(tmp_path, project_files, file_type, child_dir): """ This test uses pytest's tmp_path facilities to create a simulated project directory, and checks that the path is correct. """ # Create project file if file_type == "file": (tmp_path / project_files).write_text("blah") elif file_type == "dir": (tmp_path / project_files).mkdir(parents=True) else: raise ValueError("Invalid input: {file_type}") # Create child dirs start_dir = tmp_path / child_dir start_dir.mkdir(parents=True) os.chdir(start_dir) # Verify the project against current work directory current_path = here() assert current_path == tmp_path