3
0
Fork 0

Compare commits

..

No commits in common. "101dbdc96b029bdfb86e9aa3f4ea6d2c20858fd9" and "f4d8d69cb27d63f98346cdd3322e4da50a8b555a" have entirely different histories.

4 changed files with 86 additions and 93 deletions

View file

@ -26,7 +26,6 @@ classifiers = [
]
requires-python = ">= 3.8"
dependencies = [
"bcrypt",
"humanize",
'importlib-metadata; python_version < "3.10"',
"importlib_resources ; python_version < '3.9'",
@ -40,7 +39,7 @@ dependencies = [
[project.optional-dependencies]
db = ["SQLAlchemy", "alembic", "alembic-postgresql-enum"]
db = ["SQLAlchemy", "alembic", "alembic-postgresql-enum", "passlib"]
docs = ["Sphinx", "sphinxcontrib-programoutput", "enum-tools[sphinx]", "furo"]
tests = ["pylint", "pytest", "pytest-cov", "tox"]

View file

@ -29,11 +29,18 @@ This defines the default :term:`auth handler`.
import secrets
import uuid as _uuid
import bcrypt
from wuttjamaican.app import GenericHandler
# nb. this only works if passlib is installed (part of 'db' extra)
try:
from passlib.context import CryptContext
except ImportError: # pragma: no cover
pass
else:
password_context = CryptContext(schemes=["bcrypt"])
class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods
"""
Base class and default implementation for the :term:`auth
@ -136,7 +143,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods
:returns: ``True`` if password matches; else ``False``.
"""
return bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8"))
return password_context.verify(password, user.password)
def get_role(self, session, key):
"""
@ -412,9 +419,7 @@ class AuthHandler(GenericHandler): # pylint: disable=too-many-public-methods
:param password: New password in plain text.
"""
user.password = bcrypt.hashpw(
password.encode("utf-8"), bcrypt.gensalt()
).decode("utf-8")
user.password = password_context.hash(password)
def get_role_administrator(self, session):
"""

View file

@ -415,8 +415,8 @@ app_title = WuttaTest
self.assertEqual(ver, version("SQLAlchemy"))
# can also specify the dist
ver = self.app.get_version(dist="progress")
self.assertEqual(ver, version("progress"))
ver = self.app.get_version(dist="passlib")
self.assertEqual(ver, version("passlib"))
def test_make_title(self):
text = self.app.make_title("foo_bar")

View file

@ -8,6 +8,9 @@ from unittest.mock import patch, MagicMock
import pytest
from wuttjamaican import conf as mod
# TODO: get rid of this eventually
from wuttjamaican import conf
from wuttjamaican.exc import ConfigurationError
from wuttjamaican.app import AppHandler
from wuttjamaican.testing import FileTestCase, ConfigTestCase
@ -18,19 +21,19 @@ class TestWuttaConfig(FileTestCase):
return mod.WuttaConfig(**kwargs)
def test_contstructor_basic(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertEqual(config.appname, "wutta")
self.assertEqual(config.files_read, [])
def test_constructor_valid_files(self):
myfile = self.write_file("my.conf", "")
config = mod.WuttaConfig(files=[myfile])
config = conf.WuttaConfig(files=[myfile])
self.assertEqual(len(config.files_read), 1)
self.assertEqual(config.files_read[0], myfile)
def test_constructor_missing_files(self):
invalid = os.path.join(self.tempdir, "invalid.conf")
self.assertRaises(FileNotFoundError, mod.WuttaConfig, files=[invalid])
self.assertRaises(FileNotFoundError, conf.WuttaConfig, files=[invalid])
def test_constructor_required_files_are_present(self):
first = self.write_file(
@ -53,7 +56,7 @@ baz = B
""",
)
config = mod.WuttaConfig(files=[second])
config = conf.WuttaConfig(files=[second])
self.assertEqual(len(config.files_read), 2)
# nb. files_read listing is in order of "priority" which is
# same the as order in which files were initially read
@ -74,7 +77,7 @@ baz = B
""",
)
self.assertRaises(FileNotFoundError, mod.WuttaConfig, files=[second])
self.assertRaises(FileNotFoundError, conf.WuttaConfig, files=[second])
def test_constructor_included_files_are_present(self):
first = self.write_file(
@ -97,7 +100,7 @@ baz = B
""",
)
config = mod.WuttaConfig(files=[second])
config = conf.WuttaConfig(files=[second])
self.assertEqual(len(config.files_read), 2)
# nb. files_read listing is in order of "priority" which is
# same the as order in which files were initially read
@ -118,7 +121,7 @@ baz = B
""",
)
config = mod.WuttaConfig(files=[second])
config = conf.WuttaConfig(files=[second])
self.assertEqual(len(config.files_read), 1)
self.assertEqual(config.files_read[0], second)
self.assertIsNone(config.get("foo.bar"))
@ -156,7 +159,7 @@ baz = C
""",
)
config = mod.WuttaConfig(files=[top, middle, base])
config = conf.WuttaConfig(files=[top, middle, base])
self.assertEqual(len(config.files_read), 3)
# nb. files_read listing is in order of "priority" which is
# same the as order in which files were initially read
@ -183,7 +186,7 @@ require = %(here)s/first.conf
""",
)
config = mod.WuttaConfig(files=[second])
config = conf.WuttaConfig(files=[second])
files = config.get_prioritized_files()
self.assertEqual(len(files), 2)
self.assertEqual(files[0], second)
@ -199,16 +202,16 @@ baz = %(__file__)s
""",
)
config = mod.WuttaConfig(files=[myconf])
config = conf.WuttaConfig(files=[myconf])
self.assertEqual(config.get("foo.bar"), f"{self.tempdir}/bar.txt")
self.assertEqual(config.get("foo.baz"), myconf)
def test_constructor_defaults(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertEqual(config.defaults, {})
self.assertIsNone(config.get("foo"))
config = mod.WuttaConfig(defaults={"foo": "bar"})
config = conf.WuttaConfig(defaults={"foo": "bar"})
self.assertEqual(config.defaults, {"foo": "bar"})
self.assertEqual(config.get("foo"), "bar")
@ -230,17 +233,17 @@ preferdb = true
)
# flags are off by default
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertFalse(config.usedb)
self.assertFalse(config.preferdb)
# but may override via constructor
config = mod.WuttaConfig(usedb=True, preferdb=True)
config = conf.WuttaConfig(usedb=True, preferdb=True)
self.assertTrue(config.usedb)
self.assertTrue(config.preferdb)
# and also may override via config file
config = mod.WuttaConfig(files=[myfile])
config = conf.WuttaConfig(files=[myfile])
self.assertTrue(config.usedb)
self.assertTrue(config.preferdb)
@ -253,12 +256,12 @@ preferdb = true
pytest.skip("test is not relevant without sqlalchemy")
# flags are off by default
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertFalse(config.usedb)
self.assertFalse(config.preferdb)
# but caller may enable the flags (if sqlalchemy available)
config = mod.WuttaConfig(usedb=True, preferdb=True)
config = conf.WuttaConfig(usedb=True, preferdb=True)
self.assertTrue(config.usedb)
self.assertTrue(config.preferdb)
@ -273,7 +276,7 @@ preferdb = true
return orig_import(name, *args, **kwargs)
with patch("builtins.__import__", side_effect=mock_import):
config = mod.WuttaConfig(usedb=True, preferdb=True)
config = conf.WuttaConfig(usedb=True, preferdb=True)
self.assertFalse(config.usedb)
self.assertFalse(config.preferdb)
@ -286,19 +289,19 @@ configure_logging = true
""",
)
with patch.object(mod.WuttaConfig, "_configure_logging") as method:
with patch.object(conf.WuttaConfig, "_configure_logging") as method:
# no logging config by default
config = mod.WuttaConfig()
config = conf.WuttaConfig()
method.assert_not_called()
# but may override via constructor
method.reset_mock()
config = mod.WuttaConfig(configure_logging=True)
config = conf.WuttaConfig(configure_logging=True)
method.assert_called_once()
# and also may override via config file
method.reset_mock()
config = mod.WuttaConfig(files=[myfile])
config = conf.WuttaConfig(files=[myfile])
method.assert_called_once()
def test_constructor_configures_logging(self):
@ -315,7 +318,7 @@ configure_logging = true
with patch("wuttjamaican.conf.logging") as logging:
# basic constructor attempts logging config
config = mod.WuttaConfig(configure_logging=True)
config = conf.WuttaConfig(configure_logging=True)
logging.config.fileConfig.assert_called_once()
# if logging config fails, error is *not* raised
@ -323,18 +326,18 @@ configure_logging = true
logging.config.fileConfig.side_effect = configparser.NoSectionError(
"logging"
)
config = mod.WuttaConfig(configure_logging=True)
config = conf.WuttaConfig(configure_logging=True)
logging.config.fileConfig.assert_called_once()
# and it works if we specify config file
logging.config.fileConfig.reset_mock()
config = mod.WuttaConfig(files=[myfile])
config = conf.WuttaConfig(files=[myfile])
logging.config.fileConfig.assert_called_once()
def test_config_has_no_app_after_init(self):
# initial config should *not* have an app yet, otherwise
# extensions cannot specify a default app handler
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertIsNone(config._app)
# but after that we can get an app okay
@ -343,7 +346,7 @@ configure_logging = true
self.assertIs(app, config._app)
def test_setdefault(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
# value is empty by default
self.assertIsNone(config.get("foo"))
@ -357,15 +360,15 @@ configure_logging = true
self.assertEqual(config.setdefault("baz", "blarg"), "blarg")
def test_get_require_with_default(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertRaises(ValueError, config.get, "foo", require=True, default="bar")
def test_get_require_missing(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertRaises(ConfigurationError, config.get, "foo", require=True)
def test_get_with_default(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
# nb. returns None if no default specified
self.assertIsNone(config.get("foo"))
self.assertEqual(config.get("foo", default="bar"), "bar")
@ -378,7 +381,7 @@ configure_logging = true
pytest.skip("test is not relevant without sqlalchemy")
# minimal config, but at least it needs db cxn info
config = mod.WuttaConfig(defaults={"wutta.db.default.url": "sqlite://"})
config = conf.WuttaConfig(defaults={"wutta.db.default.url": "sqlite://"})
session = Session()
@ -411,17 +414,17 @@ configure_logging = true
session.close()
def test_get_default(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertIsNone(config.get("foo"))
self.assertEqual(config.get("foo", default="bar"), "bar")
def test_get_require(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertIsNone(config.get("foo"))
self.assertRaises(ConfigurationError, config.get, "foo", require=True)
def test_get_require_message(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertIsNone(config.get("foo"))
try:
config.get("foo", require=True, message="makin stuff up")
@ -436,7 +439,7 @@ configure_logging = true
pytest.skip("test is not relevant without sqlalchemy")
# start out with a default value
config = mod.WuttaConfig(
config = conf.WuttaConfig(
defaults={"wutta.db.default.url": "sqlite://", "foo": "bar"}
)
self.assertEqual(config.get("foo"), "bar")
@ -476,7 +479,7 @@ configure_logging = true
session.close()
def test_get_ambiguous(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
# value is returned if key is not ambiguous
config.setdefault("foo", "bar")
@ -487,23 +490,23 @@ configure_logging = true
self.assertIsNone(config.get("foo"))
def test_require(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertRaises(ConfigurationError, config.require, "foo")
def test_get_bool(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertFalse(config.get_bool("foo.bar"))
config.setdefault("foo.bar", "true")
self.assertTrue(config.get_bool("foo.bar"))
def test_get_int(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertIsNone(config.get_int("foo.bar"))
config.setdefault("foo.bar", "42")
self.assertEqual(config.get_int("foo.bar"), 42)
def test_get_list(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertIsNone(config.get_list("foo.bar"))
config.setdefault("foo.bar", "hello world")
self.assertEqual(config.get_list("foo.bar"), ["hello", "world"])
@ -623,7 +626,7 @@ configure_logging = true
def test_get_app(self):
# default handler
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertEqual(config.default_app_handler_spec, "wuttjamaican.app:AppHandler")
app = config.get_app()
self.assertIsInstance(app, AppHandler)
@ -631,7 +634,7 @@ configure_logging = true
self.assertIs(type(app), AppHandler)
# custom default handler
config = mod.WuttaConfig()
config = conf.WuttaConfig()
config.default_app_handler_spec = "tests.test_conf:CustomAppHandler"
app = config.get_app()
self.assertIsInstance(app, CustomAppHandler)
@ -643,7 +646,7 @@ configure_logging = true
pytest.skip("test is not relevant without sqlalchemy")
# default func
config = mod.WuttaConfig()
config = conf.WuttaConfig()
self.assertEqual(
config.default_engine_maker_spec,
"wuttjamaican.db.conf:make_engine_from_config",
@ -652,7 +655,7 @@ configure_logging = true
self.assertIs(make_engine, make_engine_from_config)
# custom default func
config = mod.WuttaConfig()
config = conf.WuttaConfig()
config.default_engine_maker_spec = (
"tests.test_conf:custom_make_engine_from_config"
)
@ -660,7 +663,7 @@ configure_logging = true
self.assertIs(make_engine, custom_make_engine_from_config)
def test_production(self):
config = mod.WuttaConfig()
config = conf.WuttaConfig()
# false if not defined
self.assertFalse(config.production())
@ -680,15 +683,15 @@ def custom_make_engine_from_config():
class TestWuttaConfigExtension(TestCase):
def test_basic(self):
config = mod.WuttaConfig()
ext = mod.WuttaConfigExtension()
config = conf.WuttaConfig()
ext = conf.WuttaConfigExtension()
self.assertIsNone(ext.key)
self.assertEqual(repr(ext), "WuttaConfigExtension(key=None)")
class TestGenericDefaultFiles(TestCase):
def test_linux(self):
files = mod.generic_default_files("wuttatest")
files = conf.generic_default_files("wuttatest")
self.assertIsInstance(files, list)
self.assertTrue(len(files) > 1)
self.assertIn("/etc/wuttatest.conf", files)
@ -698,7 +701,7 @@ class TestGenericDefaultFiles(TestCase):
win32com.shell.SHGetSpecialFolderPath.return_value = r"C:" + os.sep
with patch.dict("sys.modules", **{"win32com.shell": win32com}):
with patch("wuttjamaican.conf.sys", platform="win32"):
files = mod.generic_default_files("wuttatest")
files = conf.generic_default_files("wuttatest")
self.assertIsInstance(files, list)
self.assertTrue(len(files) > 1)
self.assertIn(os.path.join("C:", "wuttatest.conf"), files)
@ -713,7 +716,7 @@ class TestGenericDefaultFiles(TestCase):
with patch("builtins.__import__", side_effect=mock_import):
with patch("wuttjamaican.conf.sys", platform="win32"):
files = mod.generic_default_files("wuttatest")
files = conf.generic_default_files("wuttatest")
self.assertIsInstance(files, list)
self.assertEqual(len(files), 0)
@ -728,11 +731,11 @@ winsvc.RattailFileMonitor = /path/to/other/file
""",
)
files = mod.get_config_paths(files=[myconf], winsvc="RattailFileMonitor")
files = conf.get_config_paths(files=[myconf], winsvc="RattailFileMonitor")
self.assertEqual(files, ["/path/to/other/file"])
def test_nonexistent_default_files(self):
files = mod.get_config_paths(
files = conf.get_config_paths(
files=None,
env_files_name="IGNORE_THIS",
default_files=["/this/does/not/exist"],
@ -752,7 +755,7 @@ class TestMakeConfig(FileTestCase):
with patch("wuttjamaican.conf.WuttaConfig") as WuttaConfig:
# generic files are used if nothing is specified
generic_default_files.return_value = [generic]
config = mod.make_config(appname="wuttatest", extend=False)
config = conf.make_config(appname="wuttatest")
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[generic], appname="wuttatest", usedb=None, preferdb=None
@ -762,7 +765,7 @@ class TestMakeConfig(FileTestCase):
generic_default_files.reset_mock()
generic_default_files.return_value = []
WuttaConfig.reset_mock()
config = mod.make_config(appname="wuttatest", extend=False)
config = conf.make_config(appname="wuttatest")
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[], appname="wuttatest", usedb=None, preferdb=None
@ -776,7 +779,7 @@ class TestMakeConfig(FileTestCase):
with patch("wuttjamaican.conf.WuttaConfig") as WuttaConfig:
# generic defaults are used if nothing specified
generic_default_files.return_value = [generic]
config = mod.make_config(appname="wuttatest", extend=False)
config = conf.make_config(appname="wuttatest")
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[generic], appname="wuttatest", usedb=None, preferdb=None
@ -785,9 +788,7 @@ class TestMakeConfig(FileTestCase):
# can specify single default file
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest", default_files=myfile, extend=False
)
config = conf.make_config(appname="wuttatest", default_files=myfile)
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with(
[myfile], appname="wuttatest", usedb=None, preferdb=None
@ -796,9 +797,7 @@ class TestMakeConfig(FileTestCase):
# can specify default files as list
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest", default_files=[myfile], extend=False
)
config = conf.make_config(appname="wuttatest", default_files=[myfile])
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with(
[myfile], appname="wuttatest", usedb=None, preferdb=None
@ -807,10 +806,8 @@ class TestMakeConfig(FileTestCase):
# can specify default files as callable
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest",
default_files=lambda appname: [myfile],
extend=False,
config = conf.make_config(
appname="wuttatest", default_files=lambda appname: [myfile]
)
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with(
@ -826,7 +823,7 @@ class TestMakeConfig(FileTestCase):
generic_default_files.return_value = [generic]
# no plus files by default
config = mod.make_config(appname="wuttatest", extend=False)
config = conf.make_config(appname="wuttatest")
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[generic], appname="wuttatest", usedb=None, preferdb=None
@ -835,9 +832,7 @@ class TestMakeConfig(FileTestCase):
# can specify single plus file
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest", plus_files=myfile, extend=False
)
config = conf.make_config(appname="wuttatest", plus_files=myfile)
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[generic, myfile], appname="wuttatest", usedb=None, preferdb=None
@ -846,9 +841,7 @@ class TestMakeConfig(FileTestCase):
# can specify plus files as list
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest", plus_files=[myfile], extend=False
)
config = conf.make_config(appname="wuttatest", plus_files=[myfile])
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[generic, myfile], appname="wuttatest", usedb=None, preferdb=None
@ -857,10 +850,8 @@ class TestMakeConfig(FileTestCase):
# can specify plus files via env
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest",
env={"WUTTATEST_CONFIG_PLUS_FILES": myfile},
extend=False,
config = conf.make_config(
appname="wuttatest", env={"WUTTATEST_CONFIG_PLUS_FILES": myfile}
)
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
@ -876,7 +867,7 @@ class TestMakeConfig(FileTestCase):
generic_default_files.return_value = [generic]
# generic files by default
config = mod.make_config(appname="wuttatest", extend=False)
config = conf.make_config(appname="wuttatest")
generic_default_files.assert_called_once_with("wuttatest")
WuttaConfig.assert_called_once_with(
[generic], appname="wuttatest", usedb=None, preferdb=None
@ -885,7 +876,7 @@ class TestMakeConfig(FileTestCase):
# can specify single primary file (nb. no default files)
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(myfile, appname="wuttatest", extend=False)
config = conf.make_config(myfile, appname="wuttatest")
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with(
[myfile], appname="wuttatest", usedb=None, preferdb=None
@ -894,7 +885,7 @@ class TestMakeConfig(FileTestCase):
# can specify primary files as list
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config([myfile], appname="wuttatest", extend=False)
config = conf.make_config([myfile], appname="wuttatest")
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with(
[myfile], appname="wuttatest", usedb=None, preferdb=None
@ -903,10 +894,8 @@ class TestMakeConfig(FileTestCase):
# can specify primary files via env
generic_default_files.reset_mock()
WuttaConfig.reset_mock()
config = mod.make_config(
appname="wuttatest",
env={"WUTTATEST_CONFIG_FILES": myfile},
extend=False,
config = conf.make_config(
appname="wuttatest", env={"WUTTATEST_CONFIG_FILES": myfile}
)
generic_default_files.assert_not_called()
WuttaConfig.assert_called_once_with(