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