diff --git a/README.md b/README.md index f107741..6cee3da 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ To start the worker, assuming the previous is available in the python path saq module.file.settings ``` +> **_Note:_** `module.file.settings` can also be a callable returning the settings dictionary. + To enqueue jobs ```python diff --git a/saq/worker.py b/saq/worker.py index 5d403a2..7f4f7cd 100644 --- a/saq/worker.py +++ b/saq/worker.py @@ -312,7 +312,12 @@ def import_settings(settings: str) -> dict[str, t.Any]: # given a.b.c, parses out a.b as the module path and c as the variable module_path, name = settings.strip().rsplit(".", 1) module = importlib.import_module(module_path) - return getattr(module, name) + settings_obj = getattr(module, name) + + if callable(settings_obj): + settings_obj = settings_obj() + + return settings_obj def start( diff --git a/tests/test_settings_import.py b/tests/test_settings_import.py new file mode 100644 index 0000000..dbcf7bd --- /dev/null +++ b/tests/test_settings_import.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import contextlib +import secrets +import sys +import tempfile +import textwrap +import unittest +from pathlib import Path + +from saq.worker import import_settings + + +class TestSettingsImport(unittest.TestCase): + def setUp(self) -> None: + self.cm = cm = contextlib.ExitStack() + + tempdir = Path(cm.enter_context(tempfile.TemporaryDirectory())) + root_module_name = "foo" + secrets.token_urlsafe(2) + file_tree = [ + tempdir / root_module_name / "__init__.py", + tempdir / root_module_name / "bar" / "__init__.py", + tempdir / root_module_name / "bar" / "settings.py", + ] + for path in file_tree: + path.parent.mkdir(exist_ok=True, parents=True) + path.touch() + + file_tree[-1].write_text( + textwrap.dedent( + """ + static = { + "functions": ["pretend_its_a_fn"], + "concurrency": 100 + } + + def factory(): + return { + "functions": ["pretend_its_some_other_fn"], + "concurrency": static["concurrency"] + 100 + } + """ + ).strip() + ) + sys.path.append(str(tempdir)) + + self.module_path = f"{root_module_name}.bar.settings" + + def tearDown(self) -> None: + self.cm.close() + + def test_imports_settings_from_module_path(self) -> None: + settings = import_settings(self.module_path + ".static") + + self.assertDictEqual( + settings, + { + "functions": ["pretend_its_a_fn"], + "concurrency": 100, + }, + ) + + def test_calls_settings_factory(self) -> None: + settings = import_settings(self.module_path + ".factory") + + self.assertDictEqual( + settings, + { + "functions": ["pretend_its_some_other_fn"], + "concurrency": 200, + }, + )