python-simpleconf 0.5.9__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,30 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: python-simpleconf
3
- Version: 0.5.9
3
+ Version: 0.6.1
4
4
  Summary: Simple configuration management with python.
5
- Home-page: https://github.com/pwwang/simpleconf
6
5
  License: MIT
7
6
  Author: pwwang
8
7
  Author-email: pwwang@pwwang.com
9
- Requires-Python: >=3.7,<4.0
8
+ Requires-Python: >=3.9,<4.0
10
9
  Classifier: License :: OSI Approved :: MIT License
11
10
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.7
13
- Classifier: Programming Language :: Python :: 3.8
14
11
  Classifier: Programming Language :: Python :: 3.9
15
12
  Classifier: Programming Language :: Python :: 3.10
16
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
17
16
  Provides-Extra: all
18
17
  Provides-Extra: env
19
18
  Provides-Extra: ini
20
19
  Provides-Extra: toml
21
20
  Provides-Extra: yaml
22
- Requires-Dist: diot (>=0.2.1,<0.3.0)
21
+ Requires-Dist: diot (>=0.3.1,<0.4.0)
23
22
  Requires-Dist: iniconfig (>=2.0,<3.0) ; extra == "ini" or extra == "all"
24
- Requires-Dist: python-dotenv (>=0.21,<0.22) ; extra == "env" or extra == "all"
23
+ Requires-Dist: python-dotenv (>=1.0,<2.0) ; extra == "env" or extra == "all"
25
24
  Requires-Dist: pyyaml (>=6,<7) ; extra == "yaml" or extra == "all"
26
- Requires-Dist: rtoml (>=0.8,<0.9) ; extra == "toml" or extra == "all"
25
+ Requires-Dist: rtoml (>=0.12,<0.13) ; (sys_platform == "linux") and (extra == "toml" or extra == "all")
26
+ Requires-Dist: tomli (>=2.0,<3.0) ; (sys_platform != "linux") and (extra == "toml" or extra == "all")
27
+ Project-URL: Homepage, https://github.com/pwwang/simpleconf
27
28
  Project-URL: Repository, https://github.com/pwwang/simpleconf
28
29
  Description-Content-Type: text/markdown
29
30
 
@@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "python-simpleconf"
7
- version = "0.5.9"
7
+ version = "0.6.1"
8
8
  description = "Simple configuration management with python."
9
9
  authors = [ "pwwang <pwwang@pwwang.com>",]
10
10
  license = "MIT"
@@ -16,23 +16,25 @@ packages = [
16
16
  ]
17
17
 
18
18
  [tool.poetry.dependencies]
19
- python = "^3.7"
20
- diot = "^0.2.1"
21
- python-dotenv = { version="^0.21", optional = true}
19
+ python = "^3.9"
20
+ diot = "^0.3.1"
21
+ python-dotenv = { version="^1.0", optional = true}
22
22
  pyyaml = { version="^6", optional = true}
23
- rtoml = {version = "^0.8", optional = true}
23
+ # Use rtoml only when the wheel is available (linux)
24
+ rtoml = {version = "^0.12", optional = true, platform = "linux"}
25
+ tomli = {version = "^2.0", optional = true, markers = 'sys_platform != "linux"'}
24
26
  iniconfig = {version = "^2.0", optional = true}
25
27
 
26
28
  [tool.poetry.extras]
27
29
  ini = [ "iniconfig" ]
28
30
  env = [ "python-dotenv"]
29
31
  yaml = [ "pyyaml"]
30
- toml = [ "rtoml"]
31
- all = [ "iniconfig", "python-dotenv", "pyyaml", "rtoml"]
32
+ toml = [ "rtoml", "tomli"]
33
+ all = [ "iniconfig", "python-dotenv", "pyyaml", "rtoml", "tomli"]
32
34
 
33
35
  [tool.poetry.dev-dependencies]
34
- pytest = "^7"
35
- pytest-cov = "^4"
36
+ pytest = "^8"
37
+ pytest-cov = "^6"
36
38
 
37
39
  [tool.pytest.ini_options]
38
40
  addopts = "-vv -p no:asyncio --cov=simpleconf --cov-report xml:cov.xml --cov-report term-missing"
@@ -43,6 +45,6 @@ console_output_style = "progress"
43
45
  junit_family = "xunit1"
44
46
 
45
47
  [tool.black]
46
- line-length = 80
48
+ line-length = 88
47
49
  target-version = ['py37', 'py38', 'py39', 'py310']
48
50
  include = '\.pyi?$'
@@ -1,3 +1,3 @@
1
1
  from .config import Config, ProfileConfig
2
2
 
3
- __version__ = "0.5.9"
3
+ __version__ = "0.6.1"
@@ -1,8 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
- from typing import TYPE_CHECKING, Any, Callable, Sequence
4
+ from typing import Any, Callable, Sequence, TYPE_CHECKING
3
5
  from ast import literal_eval
4
6
 
5
- from simpleconf.utils import require_package
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from diot import Diot
@@ -47,8 +48,10 @@ def _cast_bool(value: str) -> bool:
47
48
 
48
49
  def _cast_toml(value: str) -> Any:
49
50
  """Cast toml string"""
50
- rtoml = require_package("rtoml")
51
- return rtoml.loads(value)
51
+ from .utils import require_package
52
+
53
+ toml = require_package("rtoml", "tomllib", "tomli")
54
+ return toml.loads(value)
52
55
 
53
56
 
54
57
  int_caster = type_caster("@int:", lambda x: int(float(x)))
@@ -74,11 +77,11 @@ def cast_value(value: Any, casters: Sequence[Callable]) -> Any:
74
77
  return value
75
78
 
76
79
 
77
- def cast(conf: "Diot", casters: Sequence[Callable]) -> "Diot":
80
+ def cast(conf: Diot, casters: Sequence[Callable]) -> Diot:
78
81
  """Cast the configuration"""
79
82
  for key, value in conf.items():
80
83
  if isinstance(value, dict):
81
- conf[key] = cast(value, casters)
84
+ conf[key] = cast(value, casters) # type: ignore[arg-type]
82
85
  else:
83
86
  conf[key] = cast_value(value, casters)
84
87
  return conf
@@ -1,10 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  from contextlib import contextmanager
2
- from typing import Any, List
4
+ from typing import TYPE_CHECKING, Any, List, Generator
3
5
 
4
6
  from diot import Diot
5
7
 
6
8
  from .utils import config_to_ext, get_loader, POOL_KEY, META_KEY
7
9
 
10
+ if TYPE_CHECKING:
11
+ from .loaders import Loader
12
+
8
13
 
9
14
  class Config:
10
15
  """The configuration class"""
@@ -34,7 +39,9 @@ class Config:
34
39
 
35
40
  @staticmethod
36
41
  def load_one(
37
- config, loader: str = None, ignore_nonexist: bool = False
42
+ config,
43
+ loader: str | Loader | None = None,
44
+ ignore_nonexist: bool = False,
38
45
  ) -> Diot:
39
46
  """Load the configuration from the file
40
47
 
@@ -90,7 +97,9 @@ class ProfileConfig:
90
97
 
91
98
  @staticmethod
92
99
  def load_one(
93
- conf: Any, loader: str = None, ignore_nonexist: bool = False
100
+ conf: Any,
101
+ loader: str | Loader | None = None,
102
+ ignore_nonexist: bool = False,
94
103
  ) -> Diot:
95
104
  """Load the configuration from the file
96
105
 
@@ -167,6 +176,8 @@ class ProfileConfig:
167
176
  conf[META_KEY]["current_profile"] = profile
168
177
  conf[META_KEY]["base_profile"] = base
169
178
 
179
+ return conf
180
+
170
181
  @staticmethod
171
182
  def current_profile(conf: Diot) -> str:
172
183
  """Get the current profile"""
@@ -228,7 +239,11 @@ class ProfileConfig:
228
239
 
229
240
  @staticmethod
230
241
  @contextmanager
231
- def with_profile(conf: Diot, profile: str, base: str = "default") -> Diot:
242
+ def with_profile(
243
+ conf: Diot,
244
+ profile: str,
245
+ base: str = "default",
246
+ ) -> Generator[Diot, None, None]:
232
247
  """A context manager to use the given profile
233
248
 
234
249
  Args:
@@ -1,4 +1,3 @@
1
-
2
1
  class FormatNotSupported(Exception):
3
2
  """Raised if format not supported"""
4
3
 
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC, abstractmethod
2
4
  from os import PathLike
3
5
  from pathlib import Path
4
- from typing import TYPE_CHECKING, Any
6
+ from typing import TYPE_CHECKING, Any, Callable, List
5
7
 
6
8
  from ..caster import cast
7
9
 
@@ -11,7 +13,7 @@ if TYPE_CHECKING:
11
13
 
12
14
  class Loader(ABC):
13
15
 
14
- CASTERS = None
16
+ CASTERS: List[Callable[[str, bool], Any]] | None = None
15
17
 
16
18
  @abstractmethod
17
19
  def loading(self, conf: Any, ignore_nonexist: bool) -> "Diot":
@@ -40,7 +40,7 @@ class EnvLoader(Loader):
40
40
  return Diot()
41
41
  return Diot(dotenv.main.DotEnv(conf).dict())
42
42
 
43
- def load_with_profiles(
43
+ def load_with_profiles( # type: ignore[override]
44
44
  self,
45
45
  conf: Any,
46
46
  ignore_nonexist: bool = False,
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import warnings
2
4
  from typing import Any
3
5
  from pathlib import Path
@@ -34,13 +36,13 @@ class IniLoader(Loader):
34
36
  toml_caster,
35
37
  ]
36
38
 
37
- def loading(self, conf: Any, ignore_nonexist: bool) -> dict:
39
+ def loading(self, conf: Any, ignore_nonexist: bool) -> Diot:
38
40
  """Load the configuration from an ini-like file"""
39
41
  if not self._exists(conf, ignore_nonexist):
40
42
  return Diot(default={})
41
43
  return iniconfig.IniConfig(conf).sections
42
44
 
43
- def load(self, conf: Any, ignore_nonexist: bool = False) -> "Diot":
45
+ def load(self, conf: Any, ignore_nonexist: bool = False) -> Diot:
44
46
  """Load and cast the configuration from an ini-like file"""
45
47
  sections = self.loading(conf, ignore_nonexist)
46
48
  keys = list(sections)
@@ -58,13 +60,12 @@ class IniLoader(Loader):
58
60
 
59
61
  return cast(Diot(sections[keys[0]]), self.__class__.CASTERS)
60
62
 
61
- def load_with_profiles(
63
+ def load_with_profiles( # type: ignore[override]
62
64
  self,
63
65
  conf: Any,
64
66
  ignore_nonexist: bool = False,
65
67
  ) -> Diot:
66
- """Load and cast the configuration from an ini-like file with profiles
67
- """
68
+ """Load and cast the configuration from an ini-like file with profiles"""
68
69
  sections = self.loading(conf, ignore_nonexist)
69
70
  out = Diot()
70
71
  for k, v in sections.items():
@@ -39,10 +39,10 @@ class OsenvLoader(Loader):
39
39
  out = Diot()
40
40
  for k, v in environ.items():
41
41
  if k.startswith(prefix):
42
- out[k[len_prefix :]] = v
42
+ out[k[len_prefix:]] = v
43
43
  return out
44
44
 
45
- def load_with_profiles(
45
+ def load_with_profiles( # type: ignore[override]
46
46
  self,
47
47
  conf: Any,
48
48
  ignore_nonexist: bool = False,
@@ -8,7 +8,7 @@ from ..caster import (
8
8
  )
9
9
  from . import Loader
10
10
 
11
- rtoml = require_package("rtoml")
11
+ toml = require_package("rtoml", "tomllib", "tomli")
12
12
 
13
13
 
14
14
  class TomlLoader(Loader):
@@ -24,5 +24,9 @@ class TomlLoader(Loader):
24
24
  if not self._exists(conf, ignore_nonexist):
25
25
  return Diot()
26
26
 
27
- with open(conf) as f:
28
- return Diot(rtoml.load(f))
27
+ if toml.__name__ in ("tomli", "tomllib"): # pragma: no cover
28
+ with open(conf, "rb") as f:
29
+ return Diot(toml.load(f))
30
+
31
+ with open(conf, "r") as f: # rtoml
32
+ return Diot(toml.load(f))
File without changes
@@ -1,13 +1,12 @@
1
+ from __future__ import annotations
1
2
 
2
3
  from pathlib import Path
3
4
  from importlib import import_module
4
5
  from types import ModuleType
5
- from typing import TYPE_CHECKING, Any
6
+ from typing import Any
6
7
 
7
8
  from .exceptions import FormatNotSupported
8
-
9
- if TYPE_CHECKING:
10
- from .loaders import Loader
9
+ from .loaders import Loader
11
10
 
12
11
  POOL_KEY = "_SIMPLECONF_POOL"
13
12
  META_KEY = "_SIMPLECONF_META"
@@ -32,36 +31,58 @@ def config_to_ext(conf: Any) -> str:
32
31
  return out
33
32
 
34
33
 
35
- def get_loader(ext: str) -> "Loader":
34
+ def get_loader(ext: str | Loader) -> Loader:
36
35
  """Get the loader for the extension"""
36
+ if isinstance(ext, Loader):
37
+ return ext
38
+
37
39
  if ext == "dict":
38
40
  from .loaders.dict import DictLoader
41
+
39
42
  return DictLoader()
40
43
  if ext == "env":
41
44
  from .loaders.env import EnvLoader
45
+
42
46
  return EnvLoader()
43
47
  if ext == "ini":
44
48
  from .loaders.ini import IniLoader
49
+
45
50
  return IniLoader()
46
51
  if ext == "json":
47
52
  from .loaders.json import JsonLoader
53
+
48
54
  return JsonLoader()
49
55
  if ext == "osenv":
50
56
  from .loaders.osenv import OsenvLoader
57
+
51
58
  return OsenvLoader()
52
59
  if ext == "toml":
53
60
  from .loaders.toml import TomlLoader
61
+
54
62
  return TomlLoader()
55
63
  if ext == "yaml":
56
64
  from .loaders.yaml import YamlLoader
65
+
57
66
  return YamlLoader()
58
67
 
59
68
  raise FormatNotSupported(f"{ext} is not supported.")
60
69
 
61
70
 
62
- def require_package(package: str) -> "ModuleType":
71
+ def require_package(package: str, *fallbacks: str) -> ModuleType:
63
72
  """Require the package and return the module"""
64
73
  try:
65
74
  return import_module(package)
66
75
  except ModuleNotFoundError:
67
- raise ImportError(f"{package} is not installed.")
76
+ for fallback in fallbacks:
77
+ try:
78
+ return import_module(fallback)
79
+ except ModuleNotFoundError:
80
+ pass
81
+
82
+ if fallbacks:
83
+ raise ImportError(
84
+ f"Neither '{package}' nor its fallbacks "
85
+ f"`{', '.join(fallbacks)}` is installed."
86
+ ) from None
87
+ else:
88
+ raise ImportError(f"'{package}' is not installed.") from None