Skip to content

Commit 52145e1

Browse files
authored
Merge pull request #154 from pyapp-org/release/4.7.0
Release/4.7.0
2 parents 7c0d173 + af52361 commit 52145e1

File tree

8 files changed

+148
-28
lines changed

8 files changed

+148
-28
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
cd dist
3535
echo "::set-output name=source::$(ls *.tar.gz)"
3636
echo "::set-output name=wheel::$(ls *.whl)"
37-
echo "::set-output name=version::$(poetry version | awk '{print $2}')"
37+
echo "::set-output name=version::$(poetry version -s)"
3838
3939
- name: Create a Release
4040
id: create_release

HISTORY

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
4.7.0
2+
=====
3+
4+
Features
5+
--------
6+
7+
- Add reset option to settings modify context to allow settings to be completely
8+
cleared during testing. This is particularly useful for testing CLI methods.
9+
10+
111
4.6.0
212
=====
313

poetry.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "rtd_poetry"
44

55
[tool.poetry]
66
name = "pyapp"
7-
version = "4.6.0"
7+
version = "4.7.0"
88
description = "A Python application framework - Let us handle the boring stuff!"
99
authors = ["Tim Savage <tim@savage.company>"]
1010
license = "BSD-3-Clause"

src/pyapp/conf/__init__.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
>>> settings.MY_CONFIG_VALUE
1717
'foo'
1818
19+
.. note::
20+
All settings must be UPPER_CASE. If a setting is not upper case it will not
21+
be imported into the settings object.
22+
1923
The settings object also has helper methods to simplify your testing::
2024
2125
>>> from pyapp.conf import settings
@@ -30,9 +34,25 @@
3034
removed using the `del` keyword. Once the context has been exited all changes
3135
are reverted.
3236
33-
.. note::
34-
All settings must be UPPER_CASE. If a setting is not upper case it will not
35-
be imported into the settings object.
37+
Testing CLI Commands
38+
--------------------
39+
40+
When testing the CLI settings loading can be a problem if your test case loads
41+
different settings or requires changes to be applied to settings files for test
42+
cases to execute correctly.
43+
44+
To reset settings to allow the CLI to rebuild the settings object use the
45+
``reset_settings``::
46+
47+
>>> from pyapp.conf import settings
48+
>>> with settings.modify() as patch:
49+
... patch.reset_settings()
50+
... assert not settings.is_configured
51+
>>> assert settings.is_configured
52+
53+
Just like with any usage of ``settings.modify()`` the origional settings are
54+
restored once the with block is exited.
55+
3656
3757
Settings
3858
========
@@ -161,6 +181,23 @@ def __delattr__(self, item):
161181

162182
del items[item]
163183

184+
def reset_settings(self):
185+
"""
186+
Completely reset all settings (including SETTINGS_SOURCES) to allow
187+
reloading to occur.
188+
189+
This is useful for testing CLI entry points
190+
"""
191+
setting_keys = [
192+
key for key in self._container.keys if key != "SETTINGS_SOURCES"
193+
]
194+
195+
for setting_key in setting_keys:
196+
delattr(self, setting_key)
197+
198+
# Clear settings sources
199+
setattr(self, "SETTINGS_SOURCES", [])
200+
164201

165202
class Settings:
166203
"""
@@ -194,6 +231,13 @@ def is_configured(self) -> bool:
194231
"""
195232
return bool(self.SETTINGS_SOURCES)
196233

234+
@property
235+
def keys(self) -> Sequence[str]:
236+
"""
237+
All settings keys available
238+
"""
239+
return [key for key in self.__dict__ if key.isupper()]
240+
197241
def load(self, loader: Loader, apply_method=None):
198242
"""
199243
Load settings from a loader instance. A loader is an iterator that yields key/value pairs.

src/pyapp/conf/base_settings.py

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,75 @@
1717
###############################################################################
1818
# Logging
1919

20+
LOG_LOGGERS = {}
21+
"""
22+
Simple method for configuring loggers that is merged into the default
23+
logging configuration. This allows for custom loggers to be configured
24+
without needing to duplicate the entire logging configuration.
25+
26+
Example::
27+
28+
LOG_LOGGERS = {
29+
"my_package.my_module": {
30+
"level": "INFO",
31+
"handlers": ["console"]
32+
}
33+
}
34+
35+
"""
36+
37+
LOG_HANDLERS = {}
38+
"""
39+
Simple method for configuring log handlers that is merged into the default
40+
logging configuration. This allows for custom handlers to be configured
41+
without needing to duplicate the entire logging configuration.
42+
43+
By default all handlers defined in this dict are added to the `root`
44+
handler, if this is not desired set the ``non_root`` argument to ``True``.
45+
46+
See the `Logging Handlers <https://docs.python.org/3/library/logging.handlers.html>`_
47+
in the Python documentation for a complete list of builtin handlers.
48+
49+
Example::
50+
51+
LOG_HANDLERS = {
52+
"file": {
53+
"class": "logging.handlers.RotatingFileHandler",
54+
"stream": "/path/to/my/file.log",
55+
"maxBytes": 5_242_880, # 5MiB
56+
},
57+
"special_file": {
58+
"class": "logging.FileHandler",
59+
"non_root": True, # Don't assign to root logger
60+
"stream": "/path/to/my/special.log",
61+
}
62+
}
63+
64+
"""
65+
66+
2067
LOGGING: Dict[str, Any] = {}
2168
"""
2269
Logging configuration.
2370
2471
The following configuration is applied by default::
2572
2673
LOGGING = {
27-
'formatters': {
28-
'default': {
29-
'format': '%(asctime)s | %(levelname)s | %(name)s | %(message)s',
74+
"formatters": {
75+
"default": {
76+
"format": "%(asctime)s | %(levelname)s | %(name)s | %(message)s",
3077
},
3178
},
32-
'handlers': {
33-
'console': {
34-
'class': 'logging.StreamHandler',
35-
'formatter': 'default',
36-
'stream': 'ext://sys.stderr',
79+
"handlers": {
80+
"console": {
81+
"class": "logging.StreamHandler",
82+
"formatter": "default",
83+
"stream": "ext://sys.stderr",
3784
},
3885
},
39-
'root': {
40-
# 'level' : 'INFO', # Set from command line arg parser.
41-
'handlers': ['console'],
86+
"root": {
87+
# "level" : "INFO", # Set from command line arg parser.
88+
"handlers": ["console"],
4289
}
4390
}
4491

src/pyapp/conf/report.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,6 @@ def run(self):
7575
"""
7676
Run the report
7777
"""
78-
for key, setting in self.settings.__dict__.items():
79-
if key.isupper():
80-
self.output_result(key, setting)
78+
settings = self.settings
79+
for key in settings.keys:
80+
self.output_result(key, getattr(settings, key))

tests/conf/test_.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import pytest
2-
31
import pyapp.conf
2+
import pytest
43

54

65
class TestSettings:
@@ -133,3 +132,23 @@ def test_modify__multiple_changes_reversed(self, target: pyapp.conf.Settings):
133132
assert target.SETTING_2 == 2
134133
assert target.SETTING_3 == 3
135134
assert not hasattr(target, "SETTING_6")
135+
136+
def test_modify__reset_settings(self, target: pyapp.conf.Settings):
137+
known_keys = {
138+
"UPPER_VALUE",
139+
"SETTING_2",
140+
"TEST_NAMED_FACTORY",
141+
"TEST_ALIAS_FACTORY",
142+
"TEST_PROVIDERS",
143+
}
144+
145+
with target.modify() as patch:
146+
patch.reset_settings()
147+
148+
assert all(not hasattr(target, key) for key in known_keys)
149+
assert target.SETTINGS_SOURCES == []
150+
assert not target.is_configured
151+
152+
# Check items have been restored
153+
assert all(hasattr(target, key) for key in known_keys)
154+
assert target.SETTINGS_SOURCES == ["python:tests.settings"]

0 commit comments

Comments
 (0)