Skip to content

Commit

Permalink
Code refactoring
Browse files Browse the repository at this point in the history
Improve optional import logic;
Update Travis-CI config;
Update requirements.txt and linter configs.
  • Loading branch information
yar-kik committed Jan 7, 2023
1 parent 1daf68f commit 53e8037
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 116 deletions.
12 changes: 2 additions & 10 deletions setup.cfg → .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,5 @@ ignore =
WPS305
W503
exclude =
tests/*

[mypy]
python_version = 3.8
ignore_missing_imports = True
follow_imports = skip
check_untyped_defs = False
disallow_untyped_defs = True
strict_optional = True
exclude = venv|tests
tests/*,
venv*
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
dist: bionic
arch: arm64
language: python
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
install:
- pip install -r requirements.txt
- pip install coveralls
Expand Down
2 changes: 1 addition & 1 deletion app_properties/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
DeprecationWarning,
)
__all__ = ("properties", "Conjector")
__version__ = "1.4.0"
__version__ = "1.4.1"
70 changes: 50 additions & 20 deletions app_properties/config_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,36 @@
import functools
import inspect
import pathlib
import sys
import warnings
from os.path import dirname, join, normpath, sep

if sys.version_info >= (3, 11):
import tomllib
else:
tomllib = None # type: ignore

try:
import tomli
except ImportError:
tomli = None

try:
import toml
except ImportError:
toml = None # type: ignore

try:
import ujson as json
except ImportError:
import json # type: ignore

try:
import yaml
except ImportError:
yaml = None # type: ignore


_K = TypeVar("_K")
_V = TypeVar("_V")
_T = TypeVar("_T")
Expand Down Expand Up @@ -50,33 +77,36 @@ def _get_config_file(self, filename: str) -> pathlib.Path:
return file

def _get_yaml_config(self, text_content: str) -> dict:
try:
import yaml
except ImportError as e:
if yaml is None:
raise ImportError(
'"PyYAML" is not installed, run `pip install conjector[yaml]`'
) from e

# equivalent of yaml.safe_load() but faster
return yaml.load(text_content, yaml.CSafeLoader) # nosec

def _get_json_config(self, text_content: str) -> dict:
)
try:
import ujson as json
except ImportError:
import json # type: ignore
SafeLoader = yaml.CSafeLoader
except AttributeError:
SafeLoader = yaml.SafeLoader # type: ignore
# equivalent of yaml.safe_load() but could be faster with CSafeLoader
return yaml.load(text_content, SafeLoader) # nosec

def _get_json_config(self, text_content: str) -> dict:
return json.loads(text_content)

def _get_toml_config(self, text_content: str) -> dict:
try:
import toml
except ImportError as e:
raise ImportError(
'"toml" is not installed, run `pip install conjector[toml]`'
) from e

return toml.loads(text_content)
if tomllib is not None:
return tomllib.loads(text_content)
if tomli is not None:
return tomli.loads(text_content)
if toml is not None:
warnings.warn(
'Using "toml" library is deprecated. '
'It\'s recommended to use "tomli" instead.'
"To install run `pip install conjector[toml]`",
DeprecationWarning,
)
return toml.loads(text_content)
raise ImportError(
'"tomli" is not installed, run `pip install conjector[toml]`'
)

def _apply_to_key(
self, mapping: Dict[_K, _V], func: Callable[[_K], _T]
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,18 @@ use_parentheses = true
skip_gitignore = true
skip_glob = ['venv']
known_standard_library = ['typing']

[tool.pytest.ini_options]
norecursedirs = "venv"
python_classes = "*Test Test*"
filterwarnings = "ignore::UserWarning"
log_cli=1

[tool.mypy]
python_version = 3.8
ignore_missing_imports = true
follow_imports = "skip"
check_untyped_defs = false
disallow_untyped_defs = true
strict_optional = true
exclude = ["venv", "tests"]
5 changes: 0 additions & 5 deletions pytest.ini

This file was deleted.

54 changes: 2 additions & 52 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,65 +1,15 @@
attrs==22.1.0
bandit==1.7.4
black==22.10.0
bleach==5.0.1
build==0.9.0
certifi==2022.9.24
cfgv==3.3.1
charset-normalizer==2.1.1
click==8.1.3
colorama==0.4.6
commonmark==0.9.1
coverage==6.5.0
distlib==0.3.6
docutils==0.19
exceptiongroup==1.0.0
filelock==3.8.0
flake8==5.0.4
gitdb==4.0.10
GitPython==3.1.29
identify==2.5.8
idna==3.4
importlib-metadata==5.0.0
iniconfig==1.1.1
jaraco.classes==3.2.3
keyring==23.11.0
mccabe==0.7.0
more-itertools==9.0.0
isort==5.11.4
mypy==0.990
mypy-extensions==0.4.3
nodeenv==1.7.0
packaging==21.3
pathspec==0.10.2
pbr==5.11.0
pep517==0.13.0
pkginfo==1.8.3
platformdirs==2.5.2
pluggy==1.0.0
pre-commit==2.20.0
pycodestyle==2.9.1
pyflakes==2.5.0
Pygments==2.13.0
pyparsing==3.0.9
pytest==7.2.0
pywin32-ctypes==0.2.0
PyYAML==6.0
readme-renderer==37.3
requests==2.28.1
requests-toolbelt==0.10.1
rfc3986==2.0.0
rich==12.6.0
six==1.16.0
smmap==5.0.0
stevedore==4.1.1
toml==0.10.2
tomli==2.0.1
twine==4.0.1
types-PyYAML==6.0.12.1
types-toml==0.10.8.1
types-ujson==5.6.0.0
typing_extensions==4.4.0
ujson==5.6.0
urllib3==1.26.12
virtualenv==20.16.6
webencodings==0.5.1
zipp==3.10.0
ujson==5.6.0
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
extras_require={
"yaml": ["PyYAML>=6.0"],
Expand Down
84 changes: 56 additions & 28 deletions tests/test_config/test_config_formats.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import contextlib
import importlib
import pytest
import sys
from unittest.mock import patch

from app_properties import properties

Expand All @@ -12,15 +11,7 @@ class BaseClass:
int_var: int


@contextlib.contextmanager
def mock_import(module_name: str) -> None:
module = importlib.import_module(module_name)
sys.modules[module_name] = None
yield
sys.modules[module_name] = module


def test_yaml_config_format_ok():
def test_yaml_config_format_with_cloader():
@properties(filename="application.yml")
class YamlConfigClass(BaseClass):
pass
Expand All @@ -30,16 +21,47 @@ class YamlConfigClass(BaseClass):
assert YamlConfigClass.int_var == 12


def test_yaml_config_format_not_found():
with mock_import("yaml"):
def test_yaml_config_format_with_pyloader():
with patch.dict(sys.modules["yaml"].__dict__) as patched_yaml:
del patched_yaml["CSafeLoader"]

@properties(filename="application.yml")
class YamlConfigClass(BaseClass):
pass

assert YamlConfigClass.list_var == ["a", "b", "c"]
assert YamlConfigClass.dict_var == {"key": "value"}
assert YamlConfigClass.int_var == 12


def test_yaml_config_format_pyyaml_not_found():
with patch("app_properties.config_handler.yaml", None):
with pytest.raises(ImportError):

@properties(filename="application.yml")
class YamlConfigClass(BaseClass):
pass


def test_toml_config_format_ok():
@pytest.mark.skipif(
sys.version_info < (3, 11),
reason="'tomllib' is available only for python with version >= 3.11",
)
def test_toml_config_format_tomllib_ok():
@properties(filename="application.toml")
class TomlConfigClass(BaseClass):
pass

assert TomlConfigClass.list_var == ["a", "b", "c"]
assert TomlConfigClass.dict_var == {"key": "value"}
assert TomlConfigClass.int_var == 12


@pytest.mark.skipif(
sys.version_info >= (3, 11),
reason="'tomllib' is available by default for python with version >= 3.11",
)
def test_toml_config_format_tomli_ok():
@properties(filename="application.toml")
class TomlConfigClass(BaseClass):
pass
Expand All @@ -49,8 +71,26 @@ class TomlConfigClass(BaseClass):
assert TomlConfigClass.int_var == 12


def test_toml_config_format_not_found():
with mock_import("toml"):
@pytest.mark.skipif(
sys.version_info >= (3, 11),
reason="'tomllib' is available by default for python with version >= 3.11",
)
def test_toml_config_format_toml_ok():
with patch("app_properties.config_handler.tomli", None):

@properties(filename="application.toml")
class TomlConfigClass(BaseClass):
pass

assert TomlConfigClass.list_var == ["a", "b", "c"]
assert TomlConfigClass.dict_var == {"key": "value"}
assert TomlConfigClass.int_var == 12


def test_toml_config_format_not_found_any_toml_parser():
with patch("app_properties.config_handler.tomllib", None), patch(
"app_properties.config_handler.tomli", None
), patch("app_properties.config_handler.toml", None):
with pytest.raises(ImportError):

@properties(filename="application.toml")
Expand All @@ -66,15 +106,3 @@ class JsonConfigClass(BaseClass):
assert JsonConfigClass.list_var == ["a", "b", "c"]
assert JsonConfigClass.dict_var == {"key": "value"}
assert JsonConfigClass.int_var == 12


def test_json_config_format_builtin_version():
with mock_import("ujson"):

@properties(filename="application.json")
class JsonConfigClass(BaseClass):
pass

assert JsonConfigClass.list_var == ["a", "b", "c"]
assert JsonConfigClass.dict_var == {"key": "value"}
assert JsonConfigClass.int_var == 12

0 comments on commit 53e8037

Please sign in to comment.