Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for find project root #1

Merged
merged 12 commits into from
May 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/lint_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: lint_and_test
on: [pull_request]

jobs:
ci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Run image
uses: abatilo/actions-poetry@v2
with:
poetry-version: "1.4.2"
- name: poetry install
run: poetry install
- name: black
run: poetry run black --check --verbose fastconfig/ tests/
- name: ruff
run: poetry run ruff check --exit-non-zero-on-fix fastconfig/ tests/
- name: isort
run: poetry run isort --check-only fastconfig/ tests/
- name: pytest
run: poetry run pytest
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.vscode/
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
style:
poetry run black fastconfig/ tests/
poetry run isort fastconfig/ tests/
poetry run ruff fastconfig/ tests/

test:
poetry run pytest --cov=fastconfig --cov-report=html
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
# fastconfig
A lightweight way to find the project root and load config


## Abstract

This library provides two functionalities:
(The config function is under development)

* A function to search for files while traversing up to the project root.
- `fastconfig.find_project_root`
- `fastconfig.is_project_root`
- `fastconfig.search`
* A function to directly build a class from a configuration file.


## Install

```bash
pip install fastconfig
```

## Usage


```python
import fastconfig
import pathlib
from typing import Optional

path: Optional[pathlib.Path] = fastconfig.search("pyproject.toml")
if path is not None:
# TODO: read config
pass
```

## Motivation

In many projects, it is common to write configuration files, read them in code, and build Config classes. I created this library to enable these functions to be implemented by simply defining a class and specifying a file name (such as pyproject.toml).

## Contribution
If you have suggestions for features or improvements to the code, please feel free to create an issue or pull request.
104 changes: 104 additions & 0 deletions fastconfig/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import os
from pathlib import Path
from typing import List, Optional, Union

__version__ = "0.1.0"
PROJECT_ROOTS: List[str] = [".hg", ".git"]
DEPTH: int = 10


def is_project_root(path: Union[str, Path]) -> bool:
"""
Check the given path is the project root directory or not.
This method determines the project root by whether the version control tool directory exists.

Args:
path (Union[str, Path]): The path to check whether it is the project root or not

Returns:
bool: The result of the project root or not
"""
if isinstance(path, str):
path = Path(path)

if path.is_file():
path = path.parent

for candidate in PROJECT_ROOTS:
if path.joinpath(candidate).is_dir():
return True
return False


def find_project_root(path: Optional[Union[str, Path]] = None) -> Optional[Path]:
"""
Return if the project root is found, or None if not

Args:
path (Optional[Union[str, Path]]): A path string or Path object to start searching, If nothing is passed, start in the current directory

Returns:
Optional[Path]: the project root path, or None if the project root is not found
"""
if path is None:
path = os.getcwd()
cnt: int = 0

candidate: Path = Path(path) if isinstance(path, str) else path
if is_project_root(candidate):
return candidate

while cnt < DEPTH:
candidate = candidate.parent
if is_project_root(candidate):
return candidate

if candidate.parent == candidate:
return None

cnt += 1
return None


def search(
target: Union[str, Path],
path: Optional[Union[str, Path]] = None,
end_up_the_project_root: bool = True,
) -> Optional[Path]:
"""
Recursively searches for files with the name of the target and returns the result

Args:
target (Union[str, Path]): Search target filename, and directory names are ignored
path (Optional[Union[str, Path]]): A path string or Path object to start searching, If nothing is passed, start in the current directory
end_up_the_project_root (bool): Whether or not to search the directory where the version control tool exists

Returns:
Optional[Path]: a path of the target file, or None if the target file is not found
"""
if isinstance(target, str):
target = Path(target)

target_name: str = target.name
if path is None:
path = os.getcwd()

cnt: int = 0
candidate: Path = Path(os.path.join(path, target_name))
if candidate.exists():
return candidate
if is_project_root(candidate):
return None

while cnt < DEPTH:
candidate = candidate.parent.parent.joinpath(target_name)
if candidate.exists():
return candidate

if candidate.parent.parent.joinpath(target_name) == candidate or (
end_up_the_project_root and is_project_root(candidate)
):
return None

cnt += 1
return None
Loading