8000 New config.EnvExtendedInterpolation; release 1.8.0 · FirebirdSQL/python3-base@0e87ce4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0e87ce4

Browse files
committed
New config.EnvExtendedInterpolation; release 1.8.0
1 parent 4ae2e61 commit 0e87ce4

File tree

10 files changed

+138
-13
lines changed

10 files changed

+138
-13
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [1.8.0] - 2024-05-03
8+
9+
### Added
10+
11+
- New `EnvExtendedInterpolation` class in `cnfig` module that extends `configparser.ExtendedInterpolation`
12+
with special handling for "env" section that returns value of specified environment
13+
variable, or empty string if such variable is not defined.
14+
715
## [1.7.2] - 2024-02-20
816

917
### Fixed

docs/changelog.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
Changelog
33
#########
44

5+
Version 1.8.0
6+
=============
7+
8+
* `~firebird.base.config` module:
9+
10+
- New `.EnvExtendedInterpolation` class that extends `configparser.ExtendedInterpolation`
11+
with special handling for "env" section that returns value of specified environment
12+
variable, or empty string if such variable is not defined.
13+
14+
515
Version 1.7.2
616
=============
717

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# -- Project information -----------------------------------------------------
2121

2222
project = 'Firebird-base'
23-
copyright = '2020-2023, The Firebird Project'
23+
copyright = '2020-2024, The Firebird Project'
2424
author = 'Pavel Císař'
2525

2626
# The short X.Y version

docs/config.txt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,21 @@ The framework is based around two classes:
3333
`.PyCode` and `.PyCallable`. It also provides special options `ConfigOption` and
3434
`ConfigListOption`.
3535

36-
Additionally, the `.ApplicationDirectoryScheme` abstract base class defines set of mostly
36+
Additionally, the `.DirectoryScheme` abstract base class defines set of mostly
3737
used application directories. The function `.get_directory_scheme()` could be then used
3838
to obtain instance that implements platform-specific standards for file-system location
3939
for these directories. Currently, only "Windows", "Linux" and "MacOS" directory schemes
4040
are supported.
4141

42-
.. note::
43-
You may use `platform.system` call to determine the scheme name suitable for platform
42+
.. tip::
43+
You may use `.get_directory_scheme()` function to get the scheme suitable for platform
4444
where your application is running.
4545

46+
.. tip::
47+
If your configurations contain secrets like passwords or access tokens, that would be
48+
read from files via `configparser`, you should consider to use `.EnvExtendedInterpolation`
49+
that has support for option values defined via environment variables.
50+
4651
Usage
4752
-----
4853
First, you need to define your own configuration.
@@ -288,7 +293,7 @@ The protobuf message is defined in :file:`/proto/config.proto`.
288293
cfg_msg = ConfigProto()
289294

290295
Because the proto file is NOT registered in `.protobuf` registry, you must register
291-
it manually. The proto file is listed in `setup.cfg` under *"firebird.base.protobuf"*
296+
it manually. The proto file is listed in `pyproject.toml` under *"firebird.base.protobuf"*
292297
entrypoint, so use `load_registered('firebird.base.protobuf')` for its registration.
293298

294299
.. code-block::
@@ -331,6 +336,11 @@ Application Directory Scheme
331336
.. autoclass:: MacOSDirectoryScheme
332337
.. autofunction:: get_directory_scheme
333338

339+
Configparser interpolation
340+
==========================
341+
342+
.. autoclass:: EnvExtendedInterpolation
343+
334344
Config
335345
======
336346
.. autoclass:: Config

docs/introduction.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ configuration that supports:
6969
* configuration options of various data type, including lists and other complex types
7070
* validation
7171
* direct manipulation of configuration values
72-
* reading from (and writing into) configuration in `configparser` format
72+
* reading from (and writing into) configuration in `configparser` format, including support
73+
for option values defined via environment variables (see `.EnvExtendedInterpolation`)
7374
* exchanging configuration (for example between processes) using Google protobuf messages
7475

75-
Additionally, the `.ApplicationDirectoryScheme` abstract base class defines set of mostly
76+
Additionally, the `.DirectoryScheme` abstract base class defines set of mostly
7677
used application directories. The function `.get_directory_scheme()` could be then used
7778
to obtain instance that implements platform-specific standards for file-system location
7879
for these directories. Currently, only "Windows", "Linux" and "MacOS" directory schemes

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
sphinx-bootstrap-theme>=0.8.1
22
sphinx-autodoc-typehints>=1.24.0
3-
sphinx>=5.3
3+
sphinx==7.2.6
44
.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
8181
detached = false
8282
platforms = ["linux"]
8383
dependencies = [
84-
"Sphinx>=7.2.6",
84+
"Sphinx==7.2.6",
8585
"sphinx-bootstrap-theme>=0.8.1",
8686
"sphinx-autodoc-typehints>=1.24.0",
8787
"doc2dash>=3.0.0"

src/firebird/base/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# SPDX-FileCopyrightText: 2020-present The Firebird Projects <www.firebirdsql.org>
22
#
33
# SPDX-License-Identifier: MIT
4-
__version__ = "1.7.2"
4+
__version__ = "1.8.0"

src/firebird/base/config.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@
5555
from pathlib import Path
5656
from uuid import UUID
5757
from decimal import Decimal, DecimalException
58-
from configparser import ConfigParser, DEFAULTSECT
58+
from configparser import (ConfigParser, DEFAULTSECT, ExtendedInterpolation,
59+
MAX_INTERPOLATION_DEPTH, InterpolationDepthError,
60+
InterpolationSyntaxError, NoSectionError, NoOptionError,
61+
InterpolationMissingOptionError)
5962
from inspect import signature, Signature, Parameter
6063
from enum import Enum, Flag
6164
import os
@@ -140,6 +143,74 @@ def _power_of_two(value):
140143
return False
141144
return value == 2 ** (value.bit_length() - 1)
142145

146+
class EnvExtendedInterpolation(ExtendedInterpolation):
147+
""".. versionadded:: 1.8.0
148+
149+
Modified version of `configparser.ExtendedInterpolation` class that adds special
150+
handling for "env" section that returns value of specified environment variable,
151+
or empty string if such variable is not defined.
152+
153+
Example::
154+
155+
${env:path} is reference to PATH environment variable.
156+
"""
157+
def _interpolate_some(self, parser, option, accum, rest, section, map,
158+
depth):
159+
rawval = parser.get(section, option, raw=True, fallback=rest)
160+
if depth > MAX_INTERPOLATION_DEPTH:
161+
raise InterpolationDepthError(option, section, rawval)
162+
while rest:
163+
p = rest.find("$")
164+
if p < 0:
165+
accum.append(rest)
166+
return
167+
if p > 0:
168+
accum.append(rest[:p])
169+
rest = rest[p:]
170+
# p is no longer used
171+
c = rest[1:2]
172+
if c == "$":
173+
accum.append("$")
174+
rest = rest[2:]
175+
elif c == "{":
176+
m = self._KEYCRE.match(rest)
177+
if m is None:
178+
raise InterpolationSyntaxError(option, section,
179+
"bad interpolation variable reference %r" % rest)
180+
path = m.group(1).split(':')
181+
rest = rest[m.end():]
182+
sect = section
183+
opt = option
184+
try:
185+
if len(path) == 1:
186+
opt = parser.optionxform(path[0])
187+
v = map[opt]
188+
elif len(path) == 2:
189+
sect = path[0]
190+
opt = parser.optionxform(path[1])
191+
if sect == 'env':
192+
v = os.getenv(opt.upper(), '')
193+
else:
194+
v = parser.get(sect, opt, raw=True)
195+
else:
196+
raise InterpolationSyntaxError(
197+
option, section,
198+
"More than one ':' found: %r" % (rest,))
199+
except (KeyError, NoSectionError, NoOptionError):
200+
raise InterpolationMissingOptionError(
201+
option, section, rawval, ":".join(path)) from None
202+
if "$" in v:
203+
self._interpolate_some(parser, opt, accum, v, sect,
204+
dict(parser.items(sect, raw=True)),
205+
depth + 1)
206+
else:
207+
accum.append(v)
208+
else:
209+
raise InterpolationSyntaxError(
210+
option, section,
211+
"'$' must be followed by '$' or '{', "
212+
"found: %r" % (rest,))
213+
143214
class DirectoryScheme:
144215
"""Class that provide paths to typically used app 1241 lication directories.
145216

tests/test_config.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
from enum import IntEnum, IntFlag, Flag, auto
4848
from dataclasses import dataclass
4949
from inspect import signature
50-
from configparser import ConfigParser, ExtendedInterpolation
50+
from configparser import ConfigParser
5151
from firebird.base.types import Error, ZMQAddress, MIME, PyExpr, PyCode, PyCallable
5252
from firebird.base.strconv import convert_to_str
5353
from firebird.base import config
@@ -108,7 +108,7 @@ class BaseConfigTest(TestCase):
108108
"Base class for firebird.base.config unit tests"
109109
def setUp(self):
110110
self.proto: config.ConfigProto = config.ConfigProto()
111-
self.conf: ConfigParser = ConfigParser(interpolation=ExtendedInterpolation())
111+
self.conf: ConfigParser = ConfigParser(interpolation=config.EnvExtendedInterpolation())
112112
def tearDown(self):
113113
pass
114114
def setConf(self, conf_str):
@@ -3384,6 +3384,31 @@ def test_08_linux_home_change(self, *args):
33843384
self.assertEqual(scheme.user_sync, Path('~/.local/sync/test_app').expanduser())
33853385
self.assertEqual(scheme.user_cache, Path('~/.cache/test_app').expanduser())
33863386

3387+
class TestInterpolation(BaseConfigTest):
3388+
"Unit tests for firebird.base.config.EnvExtendedInterpolation"
3389+
CONFIG = """[base]
3390+
base_value = BASE
3391+
3392+
[my-config]
3393+
value_str = VALUE
3394+
value_int = 1
3395+
base_value = ${base:base_value}
3396+
value_env_1 = ${env:mysecret}
3397+
value_env_2 = ${env:not-present}
3398+
value_env_path = ${env:path}
3399+
"""
3400+
@mock.patch.dict(os.environ, {'MYSECRET': 'secret'})
3401+
def test_01(self):
3402+
cfg = ConfigParser(interpolation=config.EnvExtendedInterpolation())
3403+
cfg.read_string(self.CONFIG)
3404+
self.assertEqual(cfg['my-config']['value_str'], 'VALUE')
3405+
self.assertEqual(cfg['my-config']['value_int'], '1')
3406+
self.assertEqual(cfg['my-config']['base_value'], 'BASE')
3407+
self.assertEqual(cfg['my-config']['value_env_1'], 'secret')
3408+
self.assertEqual(cfg['my-config']['value_env_2'], '')
3409+
self.assertEqual(cfg['my-config']['value_env_path'], os.getenv('PATH'))
3410+
3411+
33873412
if __name__ == '__main__':
33883413
unittest_main()
33893414

0 commit comments

Comments
 (0)
0