Skip to content
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

Allow runtime settings overrides #70

Merged
merged 24 commits into from
Dec 14, 2023

Conversation

PeterJCLaw
Copy link
Contributor

@PeterJCLaw PeterJCLaw commented Aug 6, 2022

Summary

This builds on #62 to introduce support for Django settings being overridden at runtime. While true changes to settings at runtime are not supported, this aims to simplify testing (django.test.override_settings should now work) and allow for settings architectures which do not pin down settings values immediately.

Consumers must ensure that settings do not change once the system has started however as this could lead to unpredictable behaviours in response. (For example it is possible that queues could have incorrect numbers of workers assigned)

I've rebased this on top of #66 so the tests that introduces help validate that extra-config loading works as expected and updated those tests to work with this new approach.

Code review

I suggest starting with the changes to app_settings.py, then looking at utils.py and test_extra_config.py.
The remainder of the changes are small adaptations to account for the slightly changed internal API and moving tests to use override_settings now that works.

itsthejoker and others added 18 commits August 6, 2022 21:21
Introduce an long-name adapter which facades away the name munging
logic and allows re-use of this both for Django settings and custom
settings. This simplifies AppSettings which can now focus on just
layering the settings.

While this doesn't gain any type checking on the settings from the
layers, that was never present anyway.
The previous approach of reloading the settings module worked ok
when the module itself was what was imported & mutated, however
with the move to a layered approach that's no longer the case.
@PeterJCLaw PeterJCLaw changed the base branch from master to extra-config-use-importlib August 6, 2022 20:57
@PeterJCLaw PeterJCLaw force-pushed the allow-runtime-settings-overrides branch from 442a4d7 to 154b929 Compare August 6, 2022 21:07
This slightly changes the behaviour of the mock_workers helpers,
however the default is an empty dictionary so this feels like a
reasonable trade-off for using the more standard override_settings
util.
@PeterJCLaw PeterJCLaw force-pushed the allow-runtime-settings-overrides branch from 154b929 to 31eb204 Compare August 6, 2022 21:10
Base automatically changed from extra-config-use-importlib to master August 15, 2022 15:15
@PeterJCLaw PeterJCLaw marked this pull request as ready for review October 17, 2022 10:24

# Allow per-queue overrides of the backend.
BACKEND_OVERRIDES = setting('BACKEND_OVERRIDES', {}) # type: Mapping[QueueName, str]
class AppSettings:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is all internal, should't this inherit from Settings? This would avoid having to use cast in django_lightweight_queue/utils.py.

I could see some concern about having the app_settings value being more explicitly mutable by adding layers, but it already is in practice (even if the typing says no) so probably not a concern here? If it is I reckon AppSettings could be made immutable and adding a layer just returns a new instance instead of doing interior mutability.

Scratch that the point in utils is to mutate the global settings so yeah I think making this an explicit part of the API is probably better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it's taken a while to get back to this. Are you suggesting that add_layer should be part of the public interface? I had avoided doing that since the consequences of calling it arbitrarily aren't well defined. That said, this is meant to be internal to this package anyway, so maybe it's ok? I'll do that and add a comment clarifying that this file is internal API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for name in override_names:
short_name = name[len(constants.SETTING_NAME_PREFIX):]
setattr(app_settings, short_name, getattr(extra_settings, name))
cast(AppSettings, app_settings).add_layer(LongNameAdapter(extra_settings))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment in app_settings.py - I reckon we can make this work more cleanly without a cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lirsacc
Copy link
Contributor

lirsacc commented Jun 14, 2023

Consumers must ensure that settings do not change once the system has started however as this could lead to unpredictable behaviours in response. (For example it is possible that queues could have incorrect numbers of workers assigned)

This feels unlikely to be a huge problem but one idea to make this safe:

  • Make app_settings.app_settings a context var always accessed through the context, not as a constant
  • Add a locked() or other way to make it immutable (not sure of the API, but at it's core this would be making a copy of the django settings at a point in time.
  • Wrap all code qualifying as the system has started (mostly the management commands? or some layer just below) with a nested context setting the immutable version. Any calls to override_settings below that would essentially noop.

The caveat is that for when someone really wants to do this then we're back to override_settings not working as expected.

@PeterJCLaw
Copy link
Contributor Author

Consumers must ensure that settings do not change once the system has started however as this could lead to unpredictable behaviours in response. (For example it is possible that queues could have incorrect numbers of workers assigned)

This feels unlikely to be a huge problem but one idea to make this safe:

  • Make app_settings.app_settings a context var always accessed through the context, not as a constant

  • Add a locked() or other way to make it immutable (not sure of the API, but at it's core this would be making a copy of the django settings at a point in time.

  • Wrap all code qualifying as the system has started (mostly the management commands? or some layer just below) with a nested context setting the immutable version. Any calls to override_settings below that would essentially noop.

The caveat is that for when someone really wants to do this then we're back to override_settings not working as expected.

So the issue here really is that calls to override_settings merely modify one of the layers, in a manner which this app has no control over. (Direct modification of settings.LIGHTWEIGHT_QUEUE_WHATEVER behaves similarly).

It's not that changes could be made and that might break things, but rather that changes could be made and we have no reasonable way to detect them and update the system to reflect them. This was already a problem (e.g: the WORKERS dict could already have been modified at runtime, with potentially interesting effects), there's just slightly more nuance to it now.

I don't feel I understand how adding contextvars or freezing the settings would help here?

This avoids needing to cast the settings by instead documenting the
expected bounds of the API offered.
@PeterJCLaw-mns PeterJCLaw-mns merged commit b235120 into master Dec 14, 2023
20 checks passed
@PeterJCLaw-mns PeterJCLaw-mns deleted the allow-runtime-settings-overrides branch December 14, 2023 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants