injecting settings

classic Classic list List threaded Threaded
19 messages Options
Reply | Threaded
Open this post in threaded view
|

injecting settings

Christian González
Hi,

I know this is a bit of a question half development, half usage of django.

I'd like to create an django app which has sub apps which inject
automatic code into settings. This is not possible, as the docs say.

Are there plans in development to enable that?
It's not necessary to "change" settings at runtime. It's just necessary
to load "once" settings from sub apps once they are installed, which is
at server start anyway.

There is the AppConfig.ready() method which can do things at server start.

But my idea is: Wouldn't it be handy to have Django load settings from
apps too, e.g. with a hook like having <app>.settings.py? which are
merged into the main settings? Is this considered as security risk?

Thanks,

Christian

--
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/11e20db3-fc69-c6a9-750c-4edfb86fcc44%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

pEpkey.asc (2K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

J. Pic
Great idea Christian, actually some frameworks have this kind of feature, such as CakePHP, in which apps can also inject urls and middlewares for example.

This would be a huge step forward for the ecosystem (and when apps can share node modules you're done haha !).

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAC6Op1-31f1bimWFsWG9PA8oqu1rRErurkUFnXr6%3D8FyrWfEYg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Christian González

I myself am using an implementation like DRF does it:

https://github.com/encode/django-rest-framework/blob/e16273a6584f9657c1e5c388558e9c5c92e7ba38/rest_framework/settings.py

They create a class "APISettings", and mimic the Django behaviour.

Dand graphene-django has adapted it already and changed a bit:

https://github.com/graphql-python/graphene-django/blob/fcc3de2a90bb81a7a02f9099029da3e4aa82b06e/graphene_django/settings.py

Mind the "# Copied shamelessly from Django REST Framework" ;-)

And yes, I shamelessly copied it too in my GDAPS project.

But it's a really bad workaround, and does not function like I would "phthonically" like it.

Christian


Am 07.05.19 um 17:02 schrieb J. Pic:
Great idea Christian, actually some frameworks have this kind of feature, such as CakePHP, in which apps can also inject urls and middlewares for example.

This would be a huge step forward for the ecosystem (and when apps can share node modules you're done haha !).
--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAC6Op1-31f1bimWFsWG9PA8oqu1rRErurkUFnXr6%3D8FyrWfEYg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/95e9383b-5a5c-6bcc-58b5-5d9a3c63c262%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

pEpkey.asc (2K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Carlton Gibson-3
Also see the django-appconf app: https://github.com/django-compressor/django-appconf

This is used by django-compressor (hence it's home) and others to add (and allow overriding) per-app settings. 


--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/0bda8e4a-d308-4da1-b34e-a0c7ed5c8621%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Adam Johnson-2
In reply to this post by J. Pic
If your django apps have default settings, they can use getattr(settings, 'MY_VALUE', default)

For third party apps I've often used an object with a bit of logic to encapsulate these defaults, especially useful in the face of app changes. For example: https://github.com/adamchainz/nexus/blob/master/nexus/conf.py

If you're managing a large project you could always add some logic in your settings file to loop through your apps and pull in settings by adding attributes to globals()

On Tue, 7 May 2019 at 17:02, J. Pic <[hidden email]> wrote:
Great idea Christian, actually some frameworks have this kind of feature, such as CakePHP, in which apps can also inject urls and middlewares for example.

This would be a huge step forward for the ecosystem (and when apps can share node modules you're done haha !).

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAC6Op1-31f1bimWFsWG9PA8oqu1rRErurkUFnXr6%3D8FyrWfEYg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.


--
Adam

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAMyDDM2oHFFcJ9DFiPVrTaKgj_zC0HbZsHwF%3D8EJffMHZdopTQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Aymeric Augustin
In reply to this post by Christian González
Hello Christian,

I'm not aware of any plans in this area.

When you say this is not possible, I assume you are referring to my work on app-loading in Django 1.7. Here's what comes to mind.

There's a chicken'n'egg problems if an app modifies INSTALLED_APPS during app loading. This is a common idea, typically to implement a modular system or a plugin system. I don't think there's a good answer here. INSTALLED_APPS is a special case among settings. I'm not sure what to make of this limitation.

Users could get into complications if an app modifies a setting after another app performed some setup based on the value of this setting. This could make app ordering more tricky. You could solve that by letting users deal with ordering problems. However, I'm wary of adding one more ramification to the ordering of INSTALLED_APPS, which already controls 4 or 5 different things.

Furthermore, as I understand your proposal, settings overrides would occur as a side effect of importing a conventionally-named per-app settings module. A guiding principle in the design of app-design was to stop depending on import order because it's Very Hard to control in Python — developers do not consider that adding an import in a module should change the behaviour of their application.

Finally, this would mean that pluggable apps cannot assume that settings are immutable anymore. It would become a best practice for every pluggable app to assume that any setting can change and to deal with the consequences. I'm not enthusiastic at the prospect of adding this overhead to the ecosystem.

All these problems are consequences of allowing settings to be mutable during app-loading, while they're currently immutable in all circumstances.

On a positive note, while this isn't officially supported, from a technical standpoint, nothing prevents you from modifying settings during app-loading. You could implement a base AppConfig class that does what you want. You "just" need to ensure that the settings you're changing are taking effect on the fly or to reset any caches based on these settings.

I know there's demand for this feature; I don't know if it can be done without creating too many potential issues. I think you'll need a DEP to breaking the "settings are immutable" dogma — perhaps not a very long one if you have working code and a clear stance on the points I raised.

Best regards

-- 
Aymeric.


Le mar. 7 mai 2019 à 17:02, Christian González <[hidden email]> a écrit :
Hi,

I know this is a bit of a question half development, half usage of django.

I'd like to create an django app which has sub apps which inject
automatic code into settings. This is not possible, as the docs say.

Are there plans in development to enable that?
It's not necessary to "change" settings at runtime. It's just necessary
to load "once" settings from sub apps once they are installed, which is
at server start anyway.

There is the AppConfig.ready() method which can do things at server start.

But my idea is: Wouldn't it be handy to have Django load settings from
apps too, e.g. with a hook like having <app>.settings.py? which are
merged into the main settings? Is this considered as security risk?

Thanks,

Christian

--
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/11e20db3-fc69-c6a9-750c-4edfb86fcc44%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CANE-7mX4M09UX8fzgX6ia_nwXA%2BXCf0ShhkkWEVNHoadpFL_rA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Christian González

Hi Americ,

thanks for answering THAT elaborative (I first thought that it#s a typical newbie question).


When you say this is not possible, I assume you are referring to my work on app-loading in Django 1.7. Here's what comes to mind.
Yes.
[...]

Finally, this would mean that pluggable apps cannot assume that settings are immutable anymore. It would become a best practice for every pluggable app to assume that any setting can change and to deal with the consequences. I'm not enthusiastic at the prospect of adding this overhead to the ecosystem.

Yes and no. I wrapped my head around the chicken-egg-problem for many weeks now.

I think that immutable settings are a good thing at last, and I would never suppose to change that. settings.py is thought to be run *once*, at server start. Or at least at reload. But then, apps are reloaded too.

What I think of is something else, maybe I didn't explain it good enough:

Apps should have the possibility to add DEFAULT settings to the Django global settings. As already mentioned in my plugin system question, I don't see a problem with app/settings loading order. The loading order is done in INSTALLED_APPS, immutable, and good as it is.

App A could e.g. have a WEBPACK_LOADER variable that is imported into the global scope, and the settings.py then CAN override it.

It's that Django's settings.py SHOULD remain the master of settings. It's more about having defaults that could be supposed by modules.


The only way *I* can think of that is, like Adam Johnson just said: after INSTALLED_APPS = [...], add some kind of

from django.conf import load_app_settings
for key, value in load_app_settings(INSTALLED_APPS):
    globals()[key] = value

(Sorry for this maybe shitty code hack)

Which then loads everything from installed apps - and later lines would override these "defaults"...?

I don't even know if this is possible.

Christian

-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/2a40ed7c-9115-1f63-3590-846697dd02e1%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

pEpkey.asc (2K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Aymeric Augustin
On 7 May 2019, at 17:49, Christian González <[hidden email]> wrote:

Apps should have the possibility to add DEFAULT settings to the Django global settings. As already mentioned in my plugin system question, I don't see a problem with app/settings loading order. The loading order is done in INSTALLED_APPS, immutable, and good as it is.

App A could e.g. have a WEBPACK_LOADER variable that is imported into the global scope, and the settings.py then CAN override it.
It's that Django's settings.py SHOULD remain the master of settings. It's more about having defaults that could be supposed by modules.

I see — this is an easier question that what I assumed based in your initial email. I got sidetracked by the reference to app-loading.

If we're only talking about providing default values for settings, currently there are two straightforward solutions:

1. Like Adam suggested, access settings like this: getattr(settings, 'MY_SETTING', 'my_default').

This works well when you access settings just once, probably at import time, and cache their value.


2. Set a default value at import time:

# apps.py

from django.apps import AppConfig
from django.conf import settings

class MyAppConfig(AppConfig):
    name = 'my_app'
    verbose_name = "..."

    def ready(self):
        if not hasattr(settings, 'MY_SETTING'):
            settings.MY_SETTING = 'my_default'

If you have many settings, you can define defaults in a DEFAULT_SETTINGS dict and do this:

    def ready(self):
        for setting_name, default_value in DEFAULT_SETTINGS:
            if not hasattr(settings, setting_name):
                setattr(settings, setting_name, default_value)

If I understand correctly, you'd like to standardize a best practice and perhaps provide some helpers to make this more convenient.

Historically, Django refrained from normalizing how to define settings because for lack of a consensus on a best practice. However, this decision was mostly about how to structure per-environments settings modules, how to handle secrets, etc.

Here we're talking about something slightly different: formalizing how an application can declare default values for its own settings — essentially a per-app equivalent of Django's global_settings.py. To do this properly, we need two things:

- a good convention for declaring these settings: I would find it elegant to consider every uppercase class attribute of an AppConfig class as a setting, but that might be too magic (and perhaps backwards-incompatible);
- a way to insert them properly into the settings object: I tried to figure out how LazySettings and friends handle global_settings, unfortunately there are more use cases than I was willing to untangle tonight.

This could be an interesting new feature, if you manage to build consensus around it. I'd like to hear what others think.


On 7 May 2019, at 17:15, Christian González <[hidden email]> wrote:

I myself am using an implementation like DRF does it:

https://github.com/encode/django-rest-framework/blob/e16273a6584f9657c1e5c388558e9c5c92e7ba38/rest_framework/settings.py

They create a class "APISettings", and mimic the Django behaviour.

This is yet another approach. DRF relies on a single Django setting which is a dict containing all individual DRF settings. Then it converts that Django setting to an object and uses this abstraction to access individual settings as properties.


Best regards,

-- 
Aymeric.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/89CA0C27-41E2-495F-9BAA-4C658467D842%40polytechnique.org.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Christian González


1. Like Adam suggested, access settings like this: getattr(settings, 'MY_SETTING', 'my_default').

This works well when you access settings just once, probably at import time, and cache their value.

Ok, interesting approaches, but I think it's not exactly what I meant...

What you describe is, apps having its *own* settings, which are possibly declared in <App>Config.ready(), by first importing global settings, and having defaults if they are not present. But this is only possible/usable for the app ITSELF.

I had tested various ways of this too, but came to another problem: what if an app depends on another one, and wants to provide sane default for that one?

What I meant is best explained with an example.

Like I mentioned earlier, I am currently trying to create a "generic" plugin system for Django apps. I don't think my code is good enough for getting this into core any time, but anyway.

My approach is adding one app "gdaps" to an Django application, and this app creates a few helper classes and methods which enable automatically finding of GDAPS plugins. You can enable any Django app with that feature "pluggability" then.

And here's the settings issue: As example, I have a submodule "gdaps.frontend" which provides frontend support (Vue, React, etc.) for the to-be-created Django application. gdaps.frontend depends on DRF and django-webpack-loader.

What I would like to do now, is that this gdaps.frontend app provides *default settings* for another, namely django-webpack-loader app, meaning adding a WEBPACK_LOADER attribute to global settings.py - which is not possible by design now. At the first glance this would break encapsulation dogma, but: It's nothing more that one app depends on another app, and uses it's features. This is perfectly valid IMHO.

Concise: It's necessary because I'd like to have as much automatisms as possible with sane defaults for the user that adds GDAPS to his Django application. Just adding "gdaps.frontend" to the INSTALLED_APPS (and one management command) would then, in my imagination, provide a fully configured webpack/frontend stack, ready to go.

Which needs a configured WEBPACK_LOADER attribute in settings.py, or elsewhere, but where django-webpack-loader can receive/load it, by normal Django app loading mechanism.


The only way I could think of is iterating over all apps, and merging all their settings files into the global one. This has to be done WITHIN the settings.py.

There are some problems I can think of:

1) You have to add code to settings.py which "finds" the other plugins. Not a problem, I already do this in GDAPS.

2) if you define FOO = {"BAR": 5} as default in a sub app, you can't easily add a FOO setting in the global settings.py by FOO = {"BAZ" = 4 } - the two wouldn't be merged, but overwritten. The user writing the global settings can't know if there IS a default already.

As long as settings.py is a code file, this can't be solved easily:

-> first "importing" all apps' settings, then the global one can't be done, as you need global settings to even KNOW which apps are installed... chicken-egg-problem.

-> first importing settings.py, and then all apps would be ok, but the mechanism must be VERY sensitive to not override settings already defined in the global settings.py by settings imported afterwords from apps.

Only thing I can think of would be settings that are declared, e.g. an ini file. There are approaches - but I don't think this is the way to go neither. So I'm clueless here.

3) security: settings.py should be only readable by the webserver, other packages (and their settings files) could be readably by others, and could contain sensitive information. This is not very likely, and could be "workarounded" by "Just don't do it."


2. Set a default value at import time:

# apps.py
[...snip...]

    def ready(self):
        if not hasattr(settings, 'MY_SETTING'):
            settings.MY_SETTING = 'my_default'

This would be more like I meant it - but AFAIK, this MY_SETTING is not available globally then? E.g. by apps that are ordered below the app in INSTALLED APPS that declared MY_SETTING?

Here we're talking about something slightly different: formalizing how an application can declare default values for its own settings — essentially a per-app equivalent of Django's global_settings.py.
This is another thing, which has some connections to "my" problem, and I'd love to see that in Django too, yes.

Best regards,

Christian


-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/d77a55c1-c21a-7ecc-08e2-752a2c37f215%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

pEpkey.asc (16K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Christian González
In reply to this post by Aymeric Augustin


Sorry about writing, and not testing myself before:

2. Set a default value at import time:

# apps.py

from django.apps import AppConfig
from django.conf import settings

class MyAppConfig(AppConfig):
    name = 'my_app'
    verbose_name = "..."

    def ready(self):
        if not hasattr(settings, 'MY_SETTING'):
            settings.MY_SETTING = 'my_default'

This works.

I have an app that has the following code in <app>Config.ready():

settings.WEBPACK_LOADER.update(
    {
        "foobar": {
            "STATS_FILE": os.path.join(
                settings.BASE_DIR,
                "{}/frontend/webpack-stats.json".format(
                    os.path.abspath(os.path.dirname(__file__))
                ),
            )
        }
    }
)

And the app webpack_loader is located *after* this app in INSTALLED apps. Within webpack-loader, the settings dict WEBPACK_LOADER inclusive "foobar" is available. So this is kind of "solved" for me - but - it's a bit of a hack.

I'd really appreciate a "standard" approach from Dajngo...

Thanks for your patience, ;-)

Christian

-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/3727cd68-3ed6-1366-2277-0815ae90da5c%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

pEpkey.asc (16K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Tom Forbes
I think what you are describing is a hard problem(tm) with no clear and generic way to solve it. Django doesn’t have the concept of a dependency tree in apps, and while this is annoying in some cases there isn’t much we can do to solve it.

If I understand your proposition, it would inevitably involve adding some kind of inter-app dependency information to Django? And in your given example about overwriting rather than merging dictionaries: there is no safe way to do this. What if merging the two dicts produces an invalid setting?

While I definitely feel settings can be improved (environments!), I’m not sure if what you are proposing has much benefit outside of your specific use case. However perhaps you could share some more code to make your idea a bit clearer.

FYI, setuptools entrypoints provides a really nice generic plugin discovery system that you might be able to leverage if you are not already. Pytest uses it to discover plugins without configuration, you can check their documentation for examples.

Tom

On 7 May 2019, at 22:55, Christian González <[hidden email]> wrote:


Sorry about writing, and not testing myself before:

2. Set a default value at import time:

# apps.py

from django.apps import AppConfig
from django.conf import settings

class MyAppConfig(AppConfig):
    name = 'my_app'
    verbose_name = "..."

    def ready(self):
        if not hasattr(settings, 'MY_SETTING'):
            settings.MY_SETTING = 'my_default'

This works.

I have an app that has the following code in <app>Config.ready():

settings.WEBPACK_LOADER.update(
    {
        "foobar": {
            "STATS_FILE": os.path.join(
                settings.BASE_DIR,
                "{}/frontend/webpack-stats.json".format(
                    os.path.abspath(os.path.dirname(__file__))
                ),
            )
        }
    }
)

And the app webpack_loader is located *after* this app in INSTALLED apps. Within webpack-loader, the settings dict WEBPACK_LOADER inclusive "foobar" is available. So this is kind of "solved" for me - but - it's a bit of a hack.

I'd really appreciate a "standard" approach from Dajngo...

Thanks for your patience, ;-)

Christian

-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/3727cd68-3ed6-1366-2277-0815ae90da5c%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.
<pEpkey.asc>

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/6407D4E6-FD52-4F1F-AAC8-86C15A9CBBCC%40tomforb.es.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Dan Davis
In reply to this post by Christian González
Christian, 

I do this in my internal and private module that depends on django-cas-ng.   django-cas-ng provides default settings in an __init__.py file that predates app.py and ready.   My strategy is that if you wish to depend on another app in this way, it is best to shadow it entirely.   For example, django-cas-ng defines an authentication backend, CASBackend.   I had an empty subclass for a long time until I needed to do something else.  As Tom Forbes writes, this is a hard problem to solve.

For example, JavaScript closures have allowed npm to solve transitive dependencies in some novel ways that have introduced new problems.

Another way to solve this is to have settings that use duck typing to behave like other sorts of settings.   For instance, I support EC2 Secrets Manager and Django database configuration by having a DBConfig class that acts like a dict, even down to __copy__ and __deepcopy__, so that whenever Django asks for the database configuration, I can go get the secret.   This frees the ops guys from restarting the app when the secret is automatically rotated.

Because Python is so extensible, these things work, up to a point, but it is a hard problem.

On Tue, May 7, 2019 at 5:55 PM Christian González <[hidden email]> wrote:


Sorry about writing, and not testing myself before:

2. Set a default value at import time:

# apps.py

from django.apps import AppConfig
from django.conf import settings

class MyAppConfig(AppConfig):
    name = 'my_app'
    verbose_name = "..."

    def ready(self):
        if not hasattr(settings, 'MY_SETTING'):
            settings.MY_SETTING = 'my_default'

This works.

I have an app that has the following code in <app>Config.ready():

settings.WEBPACK_LOADER.update(
    {
        "foobar": {
            "STATS_FILE": os.path.join(
                settings.BASE_DIR,
                "{}/frontend/webpack-stats.json".format(
                    os.path.abspath(os.path.dirname(__file__))
                ),
            )
        }
    }
)

And the app webpack_loader is located *after* this app in INSTALLED apps. Within webpack-loader, the settings dict WEBPACK_LOADER inclusive "foobar" is available. So this is kind of "solved" for me - but - it's a bit of a hack.

I'd really appreciate a "standard" approach from Dajngo...

Thanks for your patience, ;-)

Christian

-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/3727cd68-3ed6-1366-2277-0815ae90da5c%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAFzonYZ4s65G4Nq4u4Xe2SobBzu9fe00qwVvR5H%3DJe9WH%3DDskg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Michal Petrucha-4
In reply to this post by Aymeric Augustin
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Hi folks,

On Tue, May 07, 2019 at 10:53:37PM +0200, Aymeric Augustin wrote:
> If we're only talking about providing default values for settings, currently there are two straightforward solutions:
>
> 1. Like Adam suggested, access settings like this: getattr(settings, 'MY_SETTING', 'my_default').
>
> This works well when you access settings just once, probably at import time, and cache their value.
>
> Here's an example: https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/backends.py#L27-L34 <https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/backends.py#L27-L34>

This approach, however, makes it impossible to use the decorators and
context managers from django.test that override settings. Of course,
there are other ways to tune those knobs in tests, but it takes away a
standard solution provided by the framework.

> 2. Set a default value at import time:
>
> # apps.py
>
> from django.apps import AppConfig
> from django.conf import settings
>
> class MyAppConfig(AppConfig):
>     name = 'my_app'
>     verbose_name = "..."
>
>     def ready(self):
>         if not hasattr(settings, 'MY_SETTING'):
>             settings.MY_SETTING = 'my_default'
>
> If you have many settings, you can define defaults in a DEFAULT_SETTINGS dict and do this:
>
>     def ready(self):
>         for setting_name, default_value in DEFAULT_SETTINGS:
>             if not hasattr(settings, setting_name):
>                 setattr(settings, setting_name, default_value)

Nice, I guess it's been a while since I spent any brain cycles on this
topic, but this solution seems much less tedious than what I'd seen
back in the days of Django 1.4. Almost feels good enough, except for
the fact that it goes against what the docs say about modifying
settings.

> > On 7 May 2019, at 17:15, Christian González <[hidden email]> wrote:
> >
> > I myself am using an implementation like DRF does it:
> >
> > https://github.com/encode/django-rest-framework/blob/e16273a6584f9657c1e5c388558e9c5c92e7ba38/rest_framework/settings.py <https://github.com/encode/django-rest-framework/blob/e16273a6584f9657c1e5c388558e9c5c92e7ba38/rest_framework/settings.py>
> > They create a class "APISettings", and mimic the Django behaviour.
> >
>
> This is yet another approach. DRF relies on a single Django setting which is a dict containing all individual DRF settings. Then it converts that Django setting to an object and uses this abstraction to access individual settings as properties.

This seems to have become one of the current best practices, but it
requires each reusable package that goes down this road to include a
bunch of boilerplate just to deal with default values for settings. I
suppose that boilerplate could be factored out into its own reusable
package, but my gut tells me that there must be a better way to handle
something as fundamental as providing defaults for settings.
Definitely a very subjective claim, but I just feel like this is
something that should be the responsibility of the framework itself.

> Here we're talking about something slightly different: formalizing how an application can declare default values for its own settings — essentially a per-app equivalent of Django's global_settings.py. To do this properly, we need two things:
>
> - a good convention for declaring these settings: I would find it elegant to consider every uppercase class attribute of an AppConfig class as a setting, but that might be too magic (and perhaps backwards-incompatible);

I definitely agree that this feels too magic-y. Maybe introducing a
class attribute such as “DEFAULT_SETTINGS” that would hold a
dictionary could work, though?

> - a way to insert them properly into the settings object: I tried to figure out how LazySettings and friends handle global_settings, unfortunately there are more use cases than I was willing to untangle tonight.

Another thing to possibly consider, what should happen if multiple
packages try to provide different defaults for the same setting? I
mean, of course, this has kind of been floated in this thread already,
but it would add one more item to the list of things affected by the
order of INSTALLED_APPS.

Cheers,

Michal
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEUX01Y24NsLRJUN8tcDtP8g8z+SUFAlzSo6gACgkQcDtP8g8z
+SX2iw//bKFrh4tfq2k1gefcSDzhOBclqK4e7er3hwq6bRhevLvIgqVo/i3pwxlj
qSVop7ZnbnNk60xsPXLzzlEokkj+QDwldAUsaswckHmdP7zs6UiYWXzvQYc7ufpA
/+lcF3LHrBdBBn77Ip0760KxRPX5kjC0gCPSLKPpiBKxwb+31jkjLHkgIzHiYmUL
VGzpxDxkL1qLNafefXgezL7+mcUb6Mgj+jnYe+zIWGjDN8HBSQfNcALiyHWY9p5q
SCMP2+dH4wL/CsTk8eUEZZwxjlk1X5t2WuoDUxZCqybf9hJG85SjqHGbwnFZI0ar
aE9BMEy+e7brZYO3sX3QFoMHM8I0PUBPIPLbQtuEDJdnp9r69TnyNDL4kA/b9T4P
MHlta99sOe0VGaXV9LoANypJVVAiIQGU3Wxz9EVdu7g+Fdd2Y3THwaWaKes6r6nC
295lnfJP0MEIO81VDTal571Zqkw+kpm0P4uJVPlI9xgnglHoYOU0Er85ajkopqSl
UzI1X7s1GXbfBvd0Ejc3NNyhZcaNOXx0LYkxXsGRd2F/WBluj9neJpZAhbrNBIdk
eeAeZa1akIs8mQgObFSLSIxTGK5L/M5R0/cLlSQStOtvFmD7mJSXLM08CbjNWVlk
plR85tQ+JNKyxjrAKj7xO7Ha0g7Oo/U9AnkIQwXTSrxncjTEKu0=
=2yXl
-----END PGP SIGNATURE-----

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/20190508093848.rvruv3qdw6vd22m3%40koniiiik.org.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Aymeric Augustin
> On 8 May 2019, at 11:38, Michal Petrucha <[hidden email]> wrote:
>
> On Tue, May 07, 2019 at 10:53:37PM +0200, Aymeric Augustin wrote:
>> If we're only talking about providing default values for settings, currently there are two straightforward solutions:
>>
>> 1. Like Adam suggested, access settings like this: getattr(settings, 'MY_SETTING', 'my_default').
>>
>> This works well when you access settings just once, probably at import time, and cache their value.
>>
>> Here's an example: https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/backends.py#L27-L34 <https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/backends.py#L27-L34>
>
> This approach, however, makes it impossible to use the decorators and
> context managers from django.test that override settings. Of course,
> there are other ways to tune those knobs in tests, but it takes away a
> standard solution provided by the framework.

I'm not sure I understand what you're referring to. The framework provides the setting_changed signal, which seems to work well here: https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/test_signals.py#L8-L18

>> DRF relies on a single Django setting which is a dict containing all individual DRF settings. Then it converts that Django setting to an object and uses this abstraction to access individual settings as properties.
>
> This seems to have become one of the current best practices, but it
> requires each reusable package that goes down this road to include a
> bunch of boilerplate just to deal with default values for settings. I
> suppose that boilerplate could be factored out into its own reusable
> package, but my gut tells me that there must be a better way to handle
> something as fundamental as providing defaults for settings.
> Definitely a very subjective claim, but I just feel like this is
> something that should be the responsibility of the framework itself.

I'm ambivalent about this. I did it myself (TEMPLATES). I'm not sure that was worth doing.

Grouping a bunch of related settings in a dict looks satisfying. However, it's less practical than giving them a common prefix, notably to override them in tests.

I suppose I could support a well-written proposal, though :-)

>> Here we're talking about something slightly different: formalizing how an application can declare default values for its own settings — essentially a per-app equivalent of Django's global_settings.py. To do this properly, we need two things:
>>
>> - a good convention for declaring these settings: I would find it elegant to consider every uppercase class attribute of an AppConfig class as a setting, but that might be too magic (and perhaps backwards-incompatible);
>
> I definitely agree that this feels too magic-y. Maybe introducing a
> class attribute such as “DEFAULT_SETTINGS” that would hold a
> dictionary could work, though?

Yes, it could work too. I considered it and didn't choose it because it increases the distance between what global_settings.py looks like and what app settings would look like. Not a very strong argument ;-)

Also there's already magic to include only uppercase variables currently: https://github.com/django/django/blob/ef9f2eb69c9396683cefa742bc7d0a0792090e8d/django/conf/__init__.py#L135-L137

>> - a way to insert them properly into the settings object: I tried to figure out how LazySettings and friends handle global_settings, unfortunately there are more use cases than I was willing to untangle tonight.
>
> Another thing to possibly consider, what should happen if multiple
> packages try to provide different defaults for the same setting? I
> mean, of course, this has kind of been floated in this thread already,
> but it would add one more item to the list of things affected by the
> order of INSTALLED_APPS.

Yes. Since the semantic is "make this value the default if there isn't a value already" (i.e. setdefault), the most intuitive behavior should be "first app in INSTALLED_APPS wins".

Best regards,

--
Aymeric.

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/20D907CE-D92E-4EF5-8EF4-8AF87F646017%40polytechnique.org.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Dan Davis

> Another thing to possibly consider, what should happen if multiple
> packages try to provide different defaults for the same setting? I
> mean, of course, this has kind of been floated in this thread already,
> but it would add one more item to the list of things affected by the
> order of INSTALLED_APPS.

Thus, why Adam called this a Hard Problem(tm).

To resolve this, there would need to be a way for an app in INSTALLED_APPS to declare that it depends on another app.   Then, despite the order in INSTALLED_APPS, the ready call could be re-ordered, or rather a new call could be introduced so that the dependent app could be informed once its parent has been initialized.    The app could also be called before its "parents" are readied, so that it can tell what the user gave in settings.   Maybe None is the default for a setting in the parent package, but 'lower' is to be the default setting in the dependent app...

I am not asserting that such a feature is worth it, just shooting the S**T.

On Wed, May 8, 2019 at 7:38 AM Aymeric Augustin <[hidden email]> wrote:
> On 8 May 2019, at 11:38, Michal Petrucha <[hidden email]> wrote:
>
> On Tue, May 07, 2019 at 10:53:37PM +0200, Aymeric Augustin wrote:
>> If we're only talking about providing default values for settings, currently there are two straightforward solutions:
>>
>> 1. Like Adam suggested, access settings like this: getattr(settings, 'MY_SETTING', 'my_default').
>>
>> This works well when you access settings just once, probably at import time, and cache their value.
>>
>> Here's an example: https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/backends.py#L27-L34 <https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/backends.py#L27-L34>
>
> This approach, however, makes it impossible to use the decorators and
> context managers from django.test that override settings. Of course,
> there are other ways to tune those knobs in tests, but it takes away a
> standard solution provided by the framework.

I'm not sure I understand what you're referring to. The framework provides the setting_changed signal, which seems to work well here: https://github.com/aaugustin/django-sesame/blob/070cdb3fcdfa6c7310d7461add328a8095148ff1/sesame/test_signals.py#L8-L18

>> DRF relies on a single Django setting which is a dict containing all individual DRF settings. Then it converts that Django setting to an object and uses this abstraction to access individual settings as properties.
>
> This seems to have become one of the current best practices, but it
> requires each reusable package that goes down this road to include a
> bunch of boilerplate just to deal with default values for settings. I
> suppose that boilerplate could be factored out into its own reusable
> package, but my gut tells me that there must be a better way to handle
> something as fundamental as providing defaults for settings.
> Definitely a very subjective claim, but I just feel like this is
> something that should be the responsibility of the framework itself.

I'm ambivalent about this. I did it myself (TEMPLATES). I'm not sure that was worth doing.

Grouping a bunch of related settings in a dict looks satisfying. However, it's less practical than giving them a common prefix, notably to override them in tests.

I suppose I could support a well-written proposal, though :-)

>> Here we're talking about something slightly different: formalizing how an application can declare default values for its own settings — essentially a per-app equivalent of Django's global_settings.py. To do this properly, we need two things:
>>
>> - a good convention for declaring these settings: I would find it elegant to consider every uppercase class attribute of an AppConfig class as a setting, but that might be too magic (and perhaps backwards-incompatible);
>
> I definitely agree that this feels too magic-y. Maybe introducing a
> class attribute such as “DEFAULT_SETTINGS” that would hold a
> dictionary could work, though?

Yes, it could work too. I considered it and didn't choose it because it increases the distance between what global_settings.py looks like and what app settings would look like. Not a very strong argument ;-)

Also there's already magic to include only uppercase variables currently: https://github.com/django/django/blob/ef9f2eb69c9396683cefa742bc7d0a0792090e8d/django/conf/__init__.py#L135-L137

>> - a way to insert them properly into the settings object: I tried to figure out how LazySettings and friends handle global_settings, unfortunately there are more use cases than I was willing to untangle tonight.
>
> Another thing to possibly consider, what should happen if multiple
> packages try to provide different defaults for the same setting? I
> mean, of course, this has kind of been floated in this thread already,
> but it would add one more item to the list of things affected by the
> order of INSTALLED_APPS.

Yes. Since the semantic is "make this value the default if there isn't a value already" (i.e. setdefault), the most intuitive behavior should be "first app in INSTALLED_APPS wins".

Best regards,

--
Aymeric.

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/20D907CE-D92E-4EF5-8EF4-8AF87F646017%40polytechnique.org.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAFzonYZA4MQWn04_eGq66Z4Y3snHp2%3DnQgQocDYTG6Pxgv4b6Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

J. Pic
In reply to this post by Tom Forbes
In the example <app>Config.ready() calls for a dict update() which will probably work for a while, before changing the update() call in the example with more elaborated code.
System checks shoud catch cases where configuration is invalid at all.

Not sure how much you can pull from entry points, if you want to keep INSTALLED_APPS, but have a hook for your app to autoconfigure itself when added maybe something like this:

[django.middleware]
DebugToolbarMiddleware = debug_toolbar.middleware.DebugToolbarMiddleware

Otherwise a freeform callbacks should be more flexible to provision common stuff apps contribute to such as urls, middlewares, template engines ...

It's true that it doesn't look like you're going to be able to meta program settings really profoundly, ie. different packages wanting different defaults, but at least seems it can automate most of the work when installing an app from the ecosystem into a new django project, that could at least hook sane defaults, for example django-debug-toolbar's AppConfig could provision middleware and urlconf and then you would only need to add debug_toolbar to INSTALLED_APPS.

Does INSTALLED_APPS allow to plugin in a given AppConfig with arguments ? Ie. in cakephp 3.7 that was just released they don't have a list such as INSTALLED_APPS, but instead you add apps as such:

# that's all they need to setup the debug toolbar in cakephp
Plugin::load('DebugKit')

# but you could load a plugin without having urls injected as such:
Plugin::load('Cors', ['routes' => false]);

Which would be equivalent to something like:

INSTALLED_APPS.load('debug_toolbar')
INSTALLED_APPS.load('otherapp', urls=False)

It looks like app maintainers really ought to give django-gdaps a shot. Perhaps Christian, another way would be to contribute gdap support to the apps you like to use in the ecosystem. Then their maintainers could benefit from it if they install gdaps and so it can propagate in the ecosystem, and you can consolidate your patterns.

Best

--

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAC6Op1_dMFwwxPwECqc8hk%3DT5HFkcMJjaE3VJ%3Dgo8W_%2B72a5Zg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

J. Pic
ERRATA in the code above, a mistake I make really often, instead of:

DebugToolbarMiddleware = debug_toolbar.middleware.DebugToolbarMiddleware

Should be:

DebugToolbarMiddleware = debug_toolbar.middleware:DebugToolbarMiddleware

In one python module I rely on this (cli2), I ended just making so that both forms work ^^

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAC6Op196pwJs0%2B_uuq4OtLg1E%2B%3D-dPjOUhp7Exk1p-1T5BS7Ww%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

Christian González
In reply to this post by J. Pic


It looks like app maintainers really ought to give django-gdaps a shot. Perhaps Christian, another way would be to contribute gdap support to the apps you like to use in the ecosystem. Then their maintainers could benefit from it if they install gdaps and so it can propagate in the ecosystem, and you can consolidate your patterns.

That's what I wanted when I created GDAPS. It should be an extension, and not replace Djangos app loading mechanism. Not all applications are designed to be pluggable, so a deterministic order in INSTALLED_APPS is fine. How I designed it, was meant to *enable* Django apps to be pluggable just by adding gdaps to INSTALLED_APPS.

But yes, this "sane defaults" what I need there would be handy in lots of other Django apps, like you mentioned django-debug-toolbar.

And it's not about settings, but also e.g. urlpatterns - apps should be able to inject them too. I found a way that works in GDAPS, using my (adapted from pyutilib) plugin system.

The main settings/urls.py file could/should always be the master - this is different in every application. Think of ordering urls.

Christian

-- 
Dr. Christian González
https://nerdocs.at

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/8b8bf367-3026-fecd-4bf1-3c13327f12a2%40nerdocs.at.
For more options, visit https://groups.google.com/d/optout.

pEpkey.asc (2K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: injecting settings

J. Pic
Maybe you would want to install an app without having their urls injected then you're going to need to do things like

INSTALLED_APPS = [
     someapp.AppConfig(urls=False)
]

Of course this is going to make complicate the settings system, but why not hook a callback in AppConfig that is executed in-between the moment settings are parsed and the moment they are loaded into Django ?

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAC6Op18yUasg74LVeW9FVQCA%3Dx%2BEnP3KhuDtRh_t8L5UtGkDkg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.