transactions.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. """
  2. Copied from raven-python.
  3. Despite being called "legacy" in some places this resolver is very much still
  4. in use.
  5. """
  6. import re
  7. from typing import TYPE_CHECKING
  8. if TYPE_CHECKING:
  9. from django.urls.resolvers import URLResolver
  10. from typing import Dict
  11. from typing import List
  12. from typing import Optional
  13. from django.urls.resolvers import URLPattern
  14. from typing import Tuple
  15. from typing import Union
  16. from re import Pattern
  17. from django import VERSION as DJANGO_VERSION
  18. if DJANGO_VERSION >= (2, 0):
  19. from django.urls.resolvers import RoutePattern
  20. else:
  21. RoutePattern = None
  22. try:
  23. from django.urls import get_resolver
  24. except ImportError:
  25. from django.core.urlresolvers import get_resolver
  26. def get_regex(resolver_or_pattern):
  27. # type: (Union[URLPattern, URLResolver]) -> Pattern[str]
  28. """Utility method for django's deprecated resolver.regex"""
  29. try:
  30. regex = resolver_or_pattern.regex
  31. except AttributeError:
  32. regex = resolver_or_pattern.pattern.regex
  33. return regex
  34. class RavenResolver:
  35. _new_style_group_matcher = re.compile(
  36. r"<(?:([^>:]+):)?([^>]+)>"
  37. ) # https://github.com/django/django/blob/21382e2743d06efbf5623e7c9b6dccf2a325669b/django/urls/resolvers.py#L245-L247
  38. _optional_group_matcher = re.compile(r"\(\?\:([^\)]+)\)")
  39. _named_group_matcher = re.compile(r"\(\?P<(\w+)>[^\)]+\)+")
  40. _non_named_group_matcher = re.compile(r"\([^\)]+\)")
  41. # [foo|bar|baz]
  42. _either_option_matcher = re.compile(r"\[([^\]]+)\|([^\]]+)\]")
  43. _camel_re = re.compile(r"([A-Z]+)([a-z])")
  44. _cache = {} # type: Dict[URLPattern, str]
  45. def _simplify(self, pattern):
  46. # type: (Union[URLPattern, URLResolver]) -> str
  47. r"""
  48. Clean up urlpattern regexes into something readable by humans:
  49. From:
  50. > "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
  51. To:
  52. > "{sport_slug}/athletes/{athlete_slug}/"
  53. """
  54. # "new-style" path patterns can be parsed directly without turning them
  55. # into regexes first
  56. if (
  57. RoutePattern is not None
  58. and hasattr(pattern, "pattern")
  59. and isinstance(pattern.pattern, RoutePattern)
  60. ):
  61. return self._new_style_group_matcher.sub(
  62. lambda m: "{%s}" % m.group(2), str(pattern.pattern._route)
  63. )
  64. result = get_regex(pattern).pattern
  65. # remove optional params
  66. # TODO(dcramer): it'd be nice to change these into [%s] but it currently
  67. # conflicts with the other rules because we're doing regexp matches
  68. # rather than parsing tokens
  69. result = self._optional_group_matcher.sub(lambda m: "%s" % m.group(1), result)
  70. # handle named groups first
  71. result = self._named_group_matcher.sub(lambda m: "{%s}" % m.group(1), result)
  72. # handle non-named groups
  73. result = self._non_named_group_matcher.sub("{var}", result)
  74. # handle optional params
  75. result = self._either_option_matcher.sub(lambda m: m.group(1), result)
  76. # clean up any outstanding regex-y characters.
  77. result = (
  78. result.replace("^", "")
  79. .replace("$", "")
  80. .replace("?", "")
  81. .replace("\\A", "")
  82. .replace("\\Z", "")
  83. .replace("//", "/")
  84. .replace("\\", "")
  85. )
  86. return result
  87. def _resolve(self, resolver, path, parents=None):
  88. # type: (URLResolver, str, Optional[List[URLResolver]]) -> Optional[str]
  89. match = get_regex(resolver).search(path) # Django < 2.0
  90. if not match:
  91. return None
  92. if parents is None:
  93. parents = [resolver]
  94. elif resolver not in parents:
  95. parents = parents + [resolver]
  96. new_path = path[match.end() :]
  97. for pattern in resolver.url_patterns:
  98. # this is an include()
  99. if not pattern.callback:
  100. match_ = self._resolve(pattern, new_path, parents)
  101. if match_:
  102. return match_
  103. continue
  104. elif not get_regex(pattern).search(new_path):
  105. continue
  106. try:
  107. return self._cache[pattern]
  108. except KeyError:
  109. pass
  110. prefix = "".join(self._simplify(p) for p in parents)
  111. result = prefix + self._simplify(pattern)
  112. if not result.startswith("/"):
  113. result = "/" + result
  114. self._cache[pattern] = result
  115. return result
  116. return None
  117. def resolve(
  118. self,
  119. path, # type: str
  120. urlconf=None, # type: Union[None, Tuple[URLPattern, URLPattern, URLResolver], Tuple[URLPattern]]
  121. ):
  122. # type: (...) -> Optional[str]
  123. resolver = get_resolver(urlconf)
  124. match = self._resolve(resolver, path)
  125. return match
  126. LEGACY_RESOLVER = RavenResolver()