-
Notifications
You must be signed in to change notification settings - Fork 6
Expand Settings to handle different Django Architectures #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting this together and breaking this out. I can definitely see why this would be a useful change 😄 (at the very least it would make override_settings
work properly, which would simplify the tests!).
Unfortunately I have a concern about the semantics of the settings overriding -- see the inline comment about what happens if one of the settings is overridden to a falsey value.
I've included comments from a first pass at the whole PR here, however I suggest we work out what the solution to the overriding is before addressing the other comments as any refactor could affect whether/how they apply.
tests/test_pause_resume.py
Outdated
@mock.patch( | ||
'django_lightweight_queue.app_settings.BACKEND', | ||
'django_lightweight_queue.app_settings.Settings.BACKEND', | ||
new='django_lightweight_queue.backends.redis.RedisBackend', | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that the settings are dynamically loaded, what would you think about moving these to using Django's override_settings
decorator rather than patching the settings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this test, swapping to
@override_settings(
LIGHTWEIGHT_QUEUE_BACKEND='django_lightweight_queue.backends.redis.RedisBackend'
)
def test_pause_resume(self) -> None:
...
...lets this test pass, but it reports that test_pause_resume_unsupported_backend
(the immediate next test) fails. I have essentially zero experience with unittest (all of my personal and professional projects use pytest
) -- do you know why this error could be happening?
The newly failing test returns the following, but I'm not sure why:
Traceback (most recent call last):
File "/home/joe/code/django-lightweight-queue/tests/test_pause_resume.py", line 87, in test_pause_resume_unsupported_backend
with self.assertRaises(CommandError):
AssertionError: CommandError not raised
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, hrm, I wonder if we need to change the test case to inherit from one of Django's base test classes in order to use override_settings
. Haven't tested it, but that might be worth a try. (I believe there is a base class which doesn't try to do any database stuff, which is likely the one we'd want if we do go that direction).
@PeterJCLaw I've refactored it to flesh out your suggestion, but I'm a little concerned that the readability has gone down. (Very pleased about the decrease in length, though.) Ready for review again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making those changes. It definitely turned out a bit more complex than I'd imagined, which doesn't feel great for settings which one would hope ought to be simple! At least the complexity is in one place though, which I think makes up for it.
I think this is nearly there though I'm concerned that we've lost the type checking around the usage of the settings (mypy can't really see through the __getattr__
usage), which I think would be great to keep if possible.
|
||
class Overrides(Settings): | ||
"""Container for holding internally overridden settings.""" | ||
pass | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my understanding: what does this provide over using Settings()
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two reasons: it makes mypy not throw a fit and since we search by getattr, starting with an empty class means that we can add whatever settings we want and if the getattr fails then it'll fall through to the next layer.
(There was originally a different comment here but it took me a minute to remember why I did it this way.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the _uses_short_names
key from the base class so that we look up the right things, and I didn't want to duplicate it in case it ever needed to change 🤷
WORKERS: Dict[QueueName, int] = {} | ||
BACKEND: str = 'django_lightweight_queue.backends.synchronous.SynchronousBackend' | ||
LOGGER_FACTORY: Union[str, Callable[[str], Logger]] = 'logging.getLogger' | ||
# Allow per-queue overrides of the backend. | ||
BACKEND_OVERRIDES: Dict[QueueName, str] = {} | ||
MIDDLEWARE: Sequence[str] = ('django_lightweight_queue.middleware.logging.LoggingMiddleware',) | ||
# Apps to ignore when looking for tasks. Apps must be specified as the dotted | ||
# name used in `INSTALLED_APPS`. This is expected to be useful when you need to | ||
# have a file called `tasks.py` within an app, but don't want | ||
# django-lightweight-queue to import that file. | ||
# Note: this _doesn't_ prevent tasks being registered from these apps. | ||
IGNORE_APPS: Sequence[str] = () | ||
REDIS_HOST: str = '127.0.0.1' | ||
REDIS_PORT: int = 6379 | ||
REDIS_PASSWORD: Optional[str] = None | ||
REDIS_PREFIX: str = "" | ||
ENABLE_PROMETHEUS: bool = False | ||
# Workers will export metrics on this port, and ports following it | ||
PROMETHEUS_START_PORT: int = 9300 | ||
ATOMIC_JOBS: bool = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we include these members, without values, on the base Settings
type?
Combined with ensuring that the app_settings
variable is typed as Settings
I think would that keep type-checking at the places in the code where the settings get used, which I think we do want.
I realise we might then need to typing.cast
the assignment of AppSettings
, but I think that's ok if so.
I'm guessing that making Settings
a protocol didn't work for some reason? (I'd assumed that would avoid the need for a cast in the assignment to app_settings
, but perhaps it doesn't?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aaah, trying this a bit myself I can see that if Settings
is a protocol mypy doesn't like the Overrides
class not having the members. If that's the blocker I think the fix is probably to type: ignore
it in that one place rather than sacrificing the settings type checking throughout.
(I thiiink there might also be a route involving a LongNameAdapter
(see other comment) which solves the typing more completely, though that's possibly a lot more involved as a change so happy to leave that for now if there's a simpler path)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly most of this silliness is related to mypy -- if I had a quarter for each time I've googled something working on this repo so far and found an open bug ticket on the mypy repo, I'd have a very nice dinner out for myself.
I'm fine with ignoring or omitting types wherever needed as long as the values themselves are clear, but it's not my repo so it's whatever's acceptable by you all.
Co-authored-by: Peter Law <[email protected]>
Co-authored-by: Peter Law <[email protected]>
Re: type checking: I admit that I have no idea how to properly pass the type hints through that |
Sure, I'm happy to have a go at this. I realise that mypy errors can be a little obtuse at times! Thanks for your efforts getting us this far :) |
Quick update -- I haven't forgotten this, just haven't had time recently. |
Thanks for building this & sorry it's taken so long to reach a conclusion here. We've ended up extending this in #70 which I've now merged. Hopefully that resolves the use-cases you had in mind. |
Expanding from #61
The settings for django-lightweight-queue expect and require that all settings are laid out in a single file and are all ready at runtime, which is not an accurate assumption for projects that use environment variables to determine which settings to load at runtime. This PR refactors the internal project settings to be similar to that of django-cors-headers, though this one is more complex because django-lightweight-queue expects to be able to rewrite every setting on its own.
This is a stacked PR with #64 and this one should be merged first.