Skip to content

Fix handling of optional guardian module #6038

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

Closed
wants to merge 6 commits into from

Conversation

blueyed
Copy link
Contributor

@blueyed blueyed commented Jun 21, 2018

Currently you cannot import something from rest_framework without
configuring Django, since accessing settings.INSTALLED_APPS for the
django-guardian throws an ImproperlyConfigured error:

from rest_framework.exceptions import ErrorDetail

~/Vcs/django-rest-framework/rest_framework/exceptions.py in <module>()
     16
     17 from rest_framework import status
---> 18 from rest_framework.compat import unicode_to_repr
     19 from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
     20

~/Vcs/django-rest-framework/rest_framework/compat.py in <module>()
    144 guardian = None
    145 try:
--> 146     if 'guardian' in settings.INSTALLED_APPS:
    147         import guardian  # noqa
    148 except ImportError:

~/Vcs/django-rest-framework/.venv/lib/python3.6/site-packages/django/conf/__init__.py in __getattr__(self, name)
     54         """Return the value of a setting and cache it in self.__dict__."""
     55         if self._wrapped is empty:
---> 56             self._setup(name)
     57         val = getattr(self._wrapped, name)
     58         self.__dict__[name] = val

~/Vcs/django-rest-framework/.venv/lib/python3.6/site-packages/django/conf/__init__.py in _setup(self, name)
     39                 "You must either define the environment variable %s "
     40                 "or call settings.configure() before accessing settings."
---> 41                 % (desc, ENVIRONMENT_VARIABLE))
     42
     43         self._wrapped = Settings(settings_module)

ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.

I've thought about just trying to import it always, but that is some
overhead for something optional, and would also regress #1712 - except
for when handling the ImproperlyConfigured from there.

Since it only gets used by a single class this patch moves the code
there.

Currently you cannot import something from rest_framework without
configuring Django, since accessing `settings.INSTALLED_APPS` for the
django-guardian throws an ImproperlyConfigured error:

    from rest_framework.exceptions import ErrorDetail

    ~/Vcs/django-rest-framework/rest_framework/exceptions.py in <module>()
         16
         17 from rest_framework import status
    ---> 18 from rest_framework.compat import unicode_to_repr
         19 from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
         20

    ~/Vcs/django-rest-framework/rest_framework/compat.py in <module>()
        144 guardian = None
        145 try:
    --> 146     if 'guardian' in settings.INSTALLED_APPS:
        147         import guardian  # noqa
        148 except ImportError:

    ~/Vcs/django-rest-framework/.venv/lib/python3.6/site-packages/django/conf/__init__.py in __getattr__(self, name)
         54         """Return the value of a setting and cache it in self.__dict__."""
         55         if self._wrapped is empty:
    ---> 56             self._setup(name)
         57         val = getattr(self._wrapped, name)
         58         self.__dict__[name] = val

    ~/Vcs/django-rest-framework/.venv/lib/python3.6/site-packages/django/conf/__init__.py in _setup(self, name)
         39                 "You must either define the environment variable %s "
         40                 "or call settings.configure() before accessing settings."
    ---> 41                 % (desc, ENVIRONMENT_VARIABLE))
         42
         43         self._wrapped = Settings(settings_module)

    ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.

I've thought about just trying to import it always, but that is some
overhead for something optional, and would also regress encode#1712 - except
for when handling the `ImproperlyConfigured` from there.

Since it only gets used by a single class this patch moves the code
there.
try:
import guardian
except ImportError:
raise ImportError('Using DjangoObjectPermissionsFilter, but django-guardian is not installed')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think changing this to an ImportError instead of AssertionError is fine?!

Copy link
Member

Choose a reason for hiding this comment

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

Probably use a RuntimeError.

Copy link
Member

Choose a reason for hiding this comment

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

We could also include a comment linking to the issue. (As we do in filter_queryset)

# Fixes (#1712). We keep the try/except for the test suite.
guardian = None
try:
if 'guardian' in settings.INSTALLED_APPS:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is the INSTALLED_APPS bit necessary? i.e. Could we not just try the import guardian? (And so keep this in compat, which is my only reservation here. Otherwise I'd be 👍)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would regress #1712 then.
And catching the ImproperlyConfigured error then (in a generic way) also could have the side effect of swallowing Django's own.

@carltongibson
Copy link
Collaborator

OK. Thanks. I’ll have a think about it. (Will need to talk to management, but it would be nice to be able to do it.)

script:
- python setup.py bdist_wheel
- tox --installpkg ./dist/djangorestframework-*.whl
- tox # test sdist
Copy link
Member

Choose a reason for hiding this comment

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

This env isn't listed in tox?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does not have to be (AFAIK).
Or at least I've assumed that tox-travis will use the explicit env, but haven't checked the build job itself.
(tox itself allows for tox -e dist-optionals)

tox.ini Outdated
@@ -14,6 +15,8 @@ DJANGO =
2.0: django20
2.1: django21
master: djangomaster
OPTIONALS =
1: optionals
Copy link
Member

Choose a reason for hiding this comment

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

Would prefer to override the [testenv] block instead of introducing an environment variable. eg,

[testenv:py36-django20-optionals
deps = 
    {[testenv]deps}
    -rrequirments/requirments-optionals.txt

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How to run it on Travis then? via an explicit TOXENV?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done it like that now (see #6041).

@rpkilby
Copy link
Member

rpkilby commented Jun 21, 2018

Hm. I'm kind of the opinion that we just break django-guardian support out of core, similar to how django-filter was broken out of core.

This is partly driven by the fact that django-guardian seems to have largely ceased development. It's been almost a year since its latest release, and during that time, there's only been ~20 odd commits, most of them related to basic maintenance.

Copy link
Member

@tomchristie tomchristie left a comment

Choose a reason for hiding this comment

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

Thanks - some comments here. Let's try to keep the change footprint as minimal as possible.

Aside: The other tack onto this would be to use import guardian in compat.py without the if in INSTALLED_APPS check, and push this issue onto Django guardian instead. (ie. should allow import prior to django.configure())

- python setup.py bdist_wheel
- tox --installpkg ./dist/djangorestframework-*.whl
- tox # test sdist

Copy link
Member

Choose a reason for hiding this comment

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

These changes don't immediately appear connected to the rest of the PR - let's drop them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are necessary to cover the change in the tests, but I can move that into a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

=> #6041

tox.ini Outdated
@@ -14,6 +15,8 @@ DJANGO =
2.0: django20
2.1: django21
master: djangomaster
OPTIONALS =
1: optionals
Copy link
Member

Choose a reason for hiding this comment

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

Can we drop this and just keep the tox.ini as-is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is required for running tests with (and without) optionals on Travis.
tox-travis uses this then.

Copy link
Member

@rpkilby rpkilby Jun 21, 2018

Choose a reason for hiding this comment

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

@blueyed see my comment above on how to override without requiring the additional factor.

try:
import guardian
except ImportError:
raise ImportError('Using DjangoObjectPermissionsFilter, but django-guardian is not installed')
Copy link
Member

Choose a reason for hiding this comment

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

Probably use a RuntimeError.

try:
import guardian
except ImportError:
raise ImportError('Using DjangoObjectPermissionsFilter, but django-guardian is not installed')
Copy link
Member

Choose a reason for hiding this comment

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

We could also include a comment linking to the issue. (As we do in filter_queryset)

@rpkilby
Copy link
Member

rpkilby commented Jun 21, 2018

push this issue onto Django guardian instead.

@tomchristie - thoughts on breaking this out into a separate package?

@tomchristie
Copy link
Member

@rpkilby - In favor. (Tho I guess that's a separate issue to this. Even as a third party lib it'd still need to use deferred import to avoid this issue, right?)

@rpkilby
Copy link
Member

rpkilby commented Jun 21, 2018

@tomchristie - As a third party package the issue is a little less critical, since you're not likely to directly import it. That said, the change is still an improvement.

I'm happy to do the work, breaking this out into a separate package. Should we do this under the encode organization, or should I place it under my personal account?

@blueyed
Copy link
Contributor Author

blueyed commented Jun 21, 2018

I've created #6041 to fix optionals first.
Then this could eventually rebased / picked up again, depending on what @rpkilby comes up with in a separate package.

@tomchristie
Copy link
Member

Personal account.

@rpkilby
Copy link
Member

rpkilby commented Jun 22, 2018

Closing in favor of #6054, given the additional testing/CI changes.

@rpkilby rpkilby closed this Jun 22, 2018
@blueyed blueyed deleted the guardian2 branch June 23, 2018 00:00
@rpkilby
Copy link
Member

rpkilby commented Jun 23, 2018

@tomchristie, I've broken out the code related to django-guardian into https://github.com/rpkilby/django-rest-framework-guardian

Specifically, this is just the DjangoObjectPermissionsFilter, as DjangoObjectPermissions actually isn't specific to django-guardian.

If everything looks good, I'll go ahead and publish django-rest-framework-guardian, and we can start deprecating here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants