Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #152 from dbatten5/read-config-from-pyproject
Read config from pyproject.toml
- Loading branch information
Showing
8 changed files
with
324 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ bandit | |
|
||
# Type checkers | ||
mypy | ||
types-toml | ||
|
||
# Formatters | ||
black | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"""Module to hold the `AutoImportConfig` class definition.""" | ||
|
||
from pathlib import Path | ||
from typing import Any, Dict, Optional, Tuple | ||
|
||
import toml | ||
|
||
from autoimport.utils import get_pyproject_path | ||
|
||
|
||
class Config: | ||
"""Defines the base `Config` and provides accessors to get config values.""" | ||
|
||
def __init__( | ||
self, | ||
config_dict: Optional[Dict[str, Any]] = None, | ||
config_path: Optional[Path] = None, | ||
) -> None: | ||
"""Initialize the config.""" | ||
self._config_dict: Dict[str, Any] = config_dict or {} | ||
self.config_path: Optional[Path] = config_path | ||
|
||
def get_option(self, option: str) -> Optional[str]: | ||
"""Return the value of a config option. | ||
Args: | ||
option (str): the config option for which to return the value | ||
Returns: | ||
The value of the given config option or `None` if it doesn't exist | ||
""" | ||
return self._config_dict.get(option) | ||
|
||
|
||
class AutoImportConfig(Config): | ||
"""Defines the autoimport `Config`.""" | ||
|
||
def __init__(self, starting_path: Optional[Path] = None) -> None: | ||
"""Initialize the config.""" | ||
config_path, config_dict = _find_config(starting_path) | ||
super().__init__(config_dict=config_dict, config_path=config_path) | ||
|
||
|
||
def _find_config( | ||
starting_path: Optional[Path] = None, | ||
) -> Tuple[Optional[Path], Dict[str, Any]]: | ||
pyproject_path: Optional[Path] = get_pyproject_path(starting_path) | ||
if pyproject_path: | ||
return pyproject_path, toml.load(pyproject_path).get("tool", {}).get( | ||
"autoimport", {} | ||
) | ||
|
||
return None, {} | ||
|
||
|
||
autoimport_config: AutoImportConfig = AutoImportConfig() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""Module to hold various utils.""" | ||
|
||
from pathlib import Path | ||
from typing import Optional | ||
|
||
PYPROJECT_FILENAME = "pyproject.toml" | ||
|
||
|
||
def path_contains_pyproject(path: Path) -> bool: | ||
"""Determine whether a `pyproject.toml` exists in the given path. | ||
Args: | ||
path (Path): the path in which to search for the `pyproject.toml` | ||
Returns: | ||
A boolean to indicate whether a `pyproject.toml` exists in the given path | ||
""" | ||
return (path / PYPROJECT_FILENAME).is_file() | ||
|
||
|
||
def get_pyproject_path(starting_path: Optional[Path] = None) -> Optional[Path]: | ||
"""Search for a `pyproject.toml` by traversing up the tree from a path. | ||
Args: | ||
starting_path (Path): an optional path from which to start searching | ||
Returns: | ||
The `Path` to the `pyproject.toml` if it exists or `None` if it doesn't | ||
""" | ||
start: Path = starting_path or Path.cwd() | ||
|
||
for path in [start, *start.parents]: | ||
if path_contains_pyproject(path): | ||
return path / PYPROJECT_FILENAME | ||
|
||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,20 @@ | ||
"""Store the classes and fixtures used throughout the tests.""" | ||
|
||
from pathlib import Path | ||
from typing import Callable, Optional | ||
|
||
import pytest | ||
|
||
|
||
@pytest.fixture() | ||
def create_tmp_file(tmp_path: Path) -> Callable: | ||
"""Fixture for creating a temporary file.""" | ||
|
||
def _create_tmp_file( | ||
content: Optional[str] = "", filename: Optional[str] = "file.txt" | ||
) -> Path: | ||
tmp_file = tmp_path / filename | ||
tmp_file.write_text(content) | ||
return tmp_file | ||
|
||
return _create_tmp_file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"""Tests for the `Config` classes.""" | ||
|
||
from pathlib import Path | ||
from typing import Callable | ||
|
||
import toml | ||
|
||
from autoimport.config import AutoImportConfig, Config | ||
|
||
|
||
class TestConfig: | ||
"""Tests for the `Config` class.""" | ||
|
||
def test_get_valid_option(self) -> None: | ||
""" | ||
Given: a `Config` instance with a `config_dict` populated, | ||
When a value is retrieved for an existing option, | ||
Then the value of the option is returned | ||
""" | ||
config_dict = {"foo": "bar"} | ||
config = Config(config_dict=config_dict) | ||
|
||
result = config.get_option("foo") | ||
|
||
assert result == "bar" | ||
|
||
def test_get_value_for_missing_option(self) -> None: | ||
""" | ||
Given: a `Config` instance with a `config_dict` populated, | ||
When: a value is retrieved for a option not defined in the `config_dict`, | ||
Then: `None` is returned | ||
""" | ||
config_dict = {"foo": "bar"} | ||
config = Config(config_dict=config_dict) | ||
|
||
result = config.get_option("baz") | ||
|
||
assert result is None | ||
|
||
def test_get_value_for_no_config_dict(self) -> None: | ||
""" | ||
Given: a `Config` instance without a given `config_dict`, | ||
When: a value is retrieved for an option, | ||
Then: `None` is returned | ||
""" | ||
config = Config() | ||
|
||
result = config.get_option("foo") | ||
|
||
assert result is None | ||
|
||
def test_given_config_path(self) -> None: | ||
""" | ||
Given: a `Config` instance with a given `config_path`, | ||
When: the `config_path` attribute is retrieved, | ||
Then: the given `config_path` is returned | ||
""" | ||
config_path = Path("/") | ||
config = Config(config_path=config_path) | ||
|
||
result = config.config_path | ||
|
||
assert result is config_path | ||
|
||
|
||
class TestAutoImportConfig: | ||
"""Tests for the `AutoImportConfig`.""" | ||
|
||
def test_valid_pyproject(self, create_tmp_file: Callable) -> None: | ||
""" | ||
Given: a valid `pyproject.toml`, | ||
When: the `AutoImportConfig` class is instantiated, | ||
Then: a config value can be retrieved | ||
""" | ||
config_toml = toml.dumps({"tool": {"autoimport": {"foo": "bar"}}}) | ||
pyproject_path = create_tmp_file(content=config_toml, filename="pyproject.toml") | ||
autoimport_config = AutoImportConfig(starting_path=pyproject_path) | ||
|
||
result = autoimport_config.get_option("foo") | ||
|
||
assert result == "bar" | ||
|
||
def test_no_pyproject(self) -> None: | ||
""" | ||
Given: no supplied `pyproject.toml`, | ||
When: the `AutoImportConfig` class is instantiated, | ||
Then: the situation is handled gracefully | ||
""" | ||
autoimport_config = AutoImportConfig(starting_path=Path("/")) | ||
|
||
result = autoimport_config.get_option("foo") | ||
|
||
assert result is None | ||
|
||
def test_valid_pyproject_with_no_autoimport_section( | ||
self, create_tmp_file: Callable | ||
) -> None: | ||
""" | ||
Given: a valid `pyproject.toml`, | ||
When: the `AutoImportConfig` class is instantiated, | ||
Then: a config value can be retrieved | ||
""" | ||
config_toml = toml.dumps({"foo": "bar"}) | ||
pyproject_path = create_tmp_file(content=config_toml, filename="pyproject.toml") | ||
autoimport_config = AutoImportConfig(starting_path=pyproject_path) | ||
|
||
result = autoimport_config.get_option("foo") | ||
|
||
assert result is None |
Oops, something went wrong.