Skip to content

Instantly share code, notes, and snippets.

@awbacker
Created November 20, 2020 22:03
Show Gist options
  • Save awbacker/4b78b91a423177bcc1db4ec3e12e25fa to your computer and use it in GitHub Desktop.
Save awbacker/4b78b91a423177bcc1db4ec3e12e25fa to your computer and use it in GitHub Desktop.
import re
from inspect import isfunction, isclass
from typing import Union, List, Callable
from django.urls import path as _django_path, include, register_converter
from rest_framework.routers import SimpleRouter
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
TYP = Union[SimpleRouter, List, Callable, APIView, ViewSetMixin, str]
def route(prefix: str, routes: TYP, name: str = None, kwargs=None, ignore_case=False):
"""
Creates a route (path) for whatever is passed in. Supports everything:
- Including urls.py: route('x/', 'app.urls')
- List of sub-paths (e.g. route('x/', [ route(..), route(...) ])
- ViewSets, APIViews, Routers: route('x/', MyViewSet, name)
- Simple view functions, and results of include()
:param prefix: Route prefix (can be '')
:param routes: A list of urls, SimpleRouter, etc
:param name: Name, for use with reverse() or for namespace
:param kwargs: Args to pass to the view function. Rarely used. Ignored if not a view function
:param ignore_case: Turn off case sensitivity. Should rarely be used
"""
def path(*path_args, **path_kwargs):
# patch up the pattern to allow ignore case & optional '?' at end
p = _django_path(*path_args, **path_kwargs)
p.pattern.regex = re.compile(
p.pattern.regex.pattern.replace("\\?$", "?$"),
re.I if ignore_case else re.U
)
return p
# route('users/', 'project.app.urls', 'users')
if isinstance(routes, str):
return path(prefix, include((routes, name), name))
# route("path/", name="namespace", routes=[
# route("sub-path/", MyView, name="my-view"),
# route("sub-way/, MyViewSet, name="subway")
# ])
if isinstance(routes, list):
return path(prefix, include((routes, name)), name=name)
# handle include() results. we should never call include(), but some apps
# like django-admin provide their urls in this format
if isinstance(routes, tuple):
if len(routes) == 3:
return path(prefix, routes)
else:
raise Exception("Don't use include() if possible, prefer "
"route('path/', 'app.urls', name='n') format")
# standard view functions handled here. APIViews/etc are not callable
if isfunction(routes):
return path(prefix, routes, name=name, kwargs=kwargs)
# router = SimpleRouter(); router.register("x", MyViewSet)
# route("path/", router, name="r")
if isinstance(routes, SimpleRouter):
# try not to use routers directly unless there is no other way
return path(prefix, include((routes.urls, name)), name=name)
# ViewSets/APIViews are passed as a class, not as *instances* of a class
if isclass(routes):
# ViewSets of all types inherit from APIView, so check for it first
# route("users/", UserViewSet, name="users")
if issubclass(routes, ViewSetMixin):
router = SimpleRouter()
router.register("", routes, basename=name)
return path(prefix, include(router.urls), name=name)
# converts the api view, but doesn't support parameters to as_view().
# route("users/", UsersView, name="users")
elif issubclass(routes, APIView):
return path(prefix, routes.as_view(), name=name)
else:
raise Exception("route(%r, %s, name=%r>): Class must be a ViewSet or ApiView" % (
prefix, routes.__name__, name
))
raise Exception("route(%r, routes=%r, name=%r): Invalid 'routes' passed" % (
prefix, routes, name
))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment