#33351: URLPattern should have a guard/check for arguments.
-------------------------------------+-------------------------------------
               Reporter:  Keryn      |          Owner:  nobody
  Knight                             |
                   Type:  New        |         Status:  new
  feature                            |
              Component:  Core       |        Version:  dev
  (URLs)                             |       Keywords:  path _path
               Severity:  Normal     |  URLPattern check
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 Apparently, however many years into using Django, I'm still capable of
 making a "newbie" mistake and getting confused. So perhaps other actual
 new users encounter similar, especially given the lack of typing
 specifiers.

 I defined a URL like so:
 {{{
 urlpatterns = [
     path("path/to/thing", MyView.as_view(), "my_view"),
 ]
 }}}

 which ... well, you either spot the issue immediately or you don't, and
 end up with the following. If you try and `resolve()` the path (eg: by
 making a request in your browser), you'll get something like:
 {{{
 In [3]: resolve("/path/to/thing")
 ~/Code/django/django/urls/base.py in resolve(path, urlconf)
      22     if urlconf is None:
      23         urlconf = get_urlconf()
 ---> 24     return get_resolver(urlconf).resolve(path)
      25
      26

 ~/Code/django/django/urls/resolvers.py in resolve(self, path)
     586             for pattern in self.url_patterns:
     587                 try:
 --> 588                     sub_match = pattern.resolve(new_path)
     589                 except Resolver404 as e:
     590                     self._extend_tried(tried, pattern,
 e.args[0].get('tried'))

 ~/Code/django/django/urls/resolvers.py in resolve(self, path)
     388             new_path, args, kwargs = match
     389             # Pass any extra_kwargs as **kwargs.
 --> 390             kwargs.update(self.default_args)
     391             return ResolverMatch(self.callback, args, kwargs,
 self.pattern.name, route=str(self.pattern))
     392

 ValueError: dictionary update sequence element #0 has length 1; 2 is
 required
 }}}

 The crux of the issue being that I ''meant'' to give the URL a name, and
 it's a super unfortunate history that `kwargs` comes ''before'' the `name`
 argument (because nearly everyone gives a URL a name, but passing static
 kwargs is ''comparatively'' infrequent). So what's actually happened is
 that `kwargs = "my_view"` and eventually `self.default_args = "my_view"`.

 If I update to `path("path/to/thing", MyView.as_view(), "my_view",
 name="my_view")`, leaving the type incorrect, I can get the following
 error via reverse, too:
 {{{
 In [4]: reverse("my_view")
 ~/Code/django/django/urls/base.py in reverse(viewname, urlconf, args,
 kwargs, current_app)
      84             resolver = get_ns_resolver(ns_pattern, resolver,
 tuple(ns_converters.items()))
      85
 ---> 86     return resolver._reverse_with_prefix(view, prefix, *args,
 **kwargs)
      87
      88

 ~/Code/django/django/urls/resolvers.py in _reverse_with_prefix(self,
 lookup_view, _prefix, *args, **kwargs)
     669                     if
 set(kwargs).symmetric_difference(params).difference(defaults):
     670                         continue
 --> 671                     if any(kwargs.get(k, v) != v for k, v in
 defaults.items()):
     672                         continue
     673                     candidate_subs = kwargs

 AttributeError: 'str' object has no attribute 'items'
 }}}

 Both of these suggest that either there should be a type-guard in `_path`
 to assert it's ''dict-ish'' (if not `None`), or a system check on
 `URLPattern` to raise a friendly message. Well, they actually continue to
 suggest to me that everything after the `view` argument should be keyword-
 only, or that kwargs should come later, but I suspect those to be a harder
 sell ;)

 This is specifically around the `kwargs`, but it doesn't look like there's
 any guarding on the `name` either, and I feel like a name of `{'test':
 'test'}` (i.e. accidentally swapped both positionals) is likely to bite &
 cause an issue somewhere.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33351>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/052.c6fb0b191ebe9727a83111c28c22e4d6%40djangoproject.com.

Reply via email to