|
76 | 76 | # The logger is created here but initialized in the debug support module
|
77 | 77 | logger = logging.getLogger("sentry_sdk.errors")
|
78 | 78 |
|
| 79 | +_installed_modules = None |
79 | 80 |
|
80 | 81 | BASE64_ALPHABET = re.compile(r"^[a-zA-Z0-9/+=]*$")
|
81 | 82 |
|
@@ -1126,58 +1127,6 @@ def strip_string(value, max_length=None):
|
1126 | 1127 | return value
|
1127 | 1128 |
|
1128 | 1129 |
|
1129 |
| -def parse_version(version): |
1130 |
| - # type: (str) -> Optional[Tuple[int, ...]] |
1131 |
| - """ |
1132 |
| - Parses a version string into a tuple of integers. |
1133 |
| - This uses the parsing loging from PEP 440: |
1134 |
| - https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions |
1135 |
| - """ |
1136 |
| - VERSION_PATTERN = r""" # noqa: N806 |
1137 |
| - v? |
1138 |
| - (?: |
1139 |
| - (?:(?P<epoch>[0-9]+)!)? # epoch |
1140 |
| - (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment |
1141 |
| - (?P<pre> # pre-release |
1142 |
| - [-_\.]? |
1143 |
| - (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) |
1144 |
| - [-_\.]? |
1145 |
| - (?P<pre_n>[0-9]+)? |
1146 |
| - )? |
1147 |
| - (?P<post> # post release |
1148 |
| - (?:-(?P<post_n1>[0-9]+)) |
1149 |
| - | |
1150 |
| - (?: |
1151 |
| - [-_\.]? |
1152 |
| - (?P<post_l>post|rev|r) |
1153 |
| - [-_\.]? |
1154 |
| - (?P<post_n2>[0-9]+)? |
1155 |
| - ) |
1156 |
| - )? |
1157 |
| - (?P<dev> # dev release |
1158 |
| - [-_\.]? |
1159 |
| - (?P<dev_l>dev) |
1160 |
| - [-_\.]? |
1161 |
| - (?P<dev_n>[0-9]+)? |
1162 |
| - )? |
1163 |
| - ) |
1164 |
| - (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version |
1165 |
| - """ |
1166 |
| - |
1167 |
| - pattern = re.compile( |
1168 |
| - r"^\s*" + VERSION_PATTERN + r"\s*$", |
1169 |
| - re.VERBOSE | re.IGNORECASE, |
1170 |
| - ) |
1171 |
| - |
1172 |
| - try: |
1173 |
| - release = pattern.match(version).groupdict()["release"] # type: ignore |
1174 |
| - release_tuple = tuple(map(int, release.split(".")[:3])) # type: Tuple[int, ...] |
1175 |
| - except (TypeError, ValueError, AttributeError): |
1176 |
| - return None |
1177 |
| - |
1178 |
| - return release_tuple |
1179 |
| - |
1180 |
| - |
1181 | 1130 | def _is_contextvars_broken():
|
1182 | 1131 | # type: () -> bool
|
1183 | 1132 | """
|
@@ -1572,6 +1521,108 @@ def is_sentry_url(hub, url):
|
1572 | 1521 | )
|
1573 | 1522 |
|
1574 | 1523 |
|
| 1524 | +def parse_version(version): |
| 1525 | + # type: (str) -> Optional[Tuple[int, ...]] |
| 1526 | + """ |
| 1527 | + Parses a version string into a tuple of integers. |
| 1528 | + This uses the parsing loging from PEP 440: |
| 1529 | + https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions |
| 1530 | + """ |
| 1531 | + VERSION_PATTERN = r""" # noqa: N806 |
| 1532 | + v? |
| 1533 | + (?: |
| 1534 | + (?:(?P<epoch>[0-9]+)!)? # epoch |
| 1535 | + (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment |
| 1536 | + (?P<pre> # pre-release |
| 1537 | + [-_\.]? |
| 1538 | + (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) |
| 1539 | + [-_\.]? |
| 1540 | + (?P<pre_n>[0-9]+)? |
| 1541 | + )? |
| 1542 | + (?P<post> # post release |
| 1543 | + (?:-(?P<post_n1>[0-9]+)) |
| 1544 | + | |
| 1545 | + (?: |
| 1546 | + [-_\.]? |
| 1547 | + (?P<post_l>post|rev|r) |
| 1548 | + [-_\.]? |
| 1549 | + (?P<post_n2>[0-9]+)? |
| 1550 | + ) |
| 1551 | + )? |
| 1552 | + (?P<dev> # dev release |
| 1553 | + [-_\.]? |
| 1554 | + (?P<dev_l>dev) |
| 1555 | + [-_\.]? |
| 1556 | + (?P<dev_n>[0-9]+)? |
| 1557 | + )? |
| 1558 | + ) |
| 1559 | + (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version |
| 1560 | + """ |
| 1561 | + |
| 1562 | + pattern = re.compile( |
| 1563 | + r"^\s*" + VERSION_PATTERN + r"\s*$", |
| 1564 | + re.VERBOSE | re.IGNORECASE, |
| 1565 | + ) |
| 1566 | + |
| 1567 | + try: |
| 1568 | + release = pattern.match(version).groupdict()["release"] # type: ignore |
| 1569 | + release_tuple = tuple(map(int, release.split(".")[:3])) # type: Tuple[int, ...] |
| 1570 | + except (TypeError, ValueError, AttributeError): |
| 1571 | + return None |
| 1572 | + |
| 1573 | + return release_tuple |
| 1574 | + |
| 1575 | + |
| 1576 | +def _generate_installed_modules(): |
| 1577 | + # type: () -> Iterator[Tuple[str, str]] |
| 1578 | + try: |
| 1579 | + from importlib import metadata |
| 1580 | + |
| 1581 | + for dist in metadata.distributions(): |
| 1582 | + name = dist.metadata["Name"] |
| 1583 | + # `metadata` values may be `None`, see: |
| 1584 | + # https://github.com/python/cpython/issues/91216 |
| 1585 | + # and |
| 1586 | + # https://github.com/python/importlib_metadata/issues/371 |
| 1587 | + if name is not None: |
| 1588 | + version = metadata.version(name) |
| 1589 | + if version is not None: |
| 1590 | + yield _normalize_module_name(name), version |
| 1591 | + |
| 1592 | + except ImportError: |
| 1593 | + # < py3.8 |
| 1594 | + try: |
| 1595 | + import pkg_resources |
| 1596 | + except ImportError: |
| 1597 | + return |
| 1598 | + |
| 1599 | + for info in pkg_resources.working_set: |
| 1600 | + yield _normalize_module_name(info.key), info.version |
| 1601 | + |
| 1602 | + |
| 1603 | +def _normalize_module_name(name): |
| 1604 | + # type: (str) -> str |
| 1605 | + return name.lower() |
| 1606 | + |
| 1607 | + |
| 1608 | +def _get_installed_modules(): |
| 1609 | + # type: () -> Dict[str, str] |
| 1610 | + global _installed_modules |
| 1611 | + if _installed_modules is None: |
| 1612 | + _installed_modules = dict(_generate_installed_modules()) |
| 1613 | + return _installed_modules |
| 1614 | + |
| 1615 | + |
| 1616 | +def package_version(package): |
| 1617 | + # type: (str) -> Optional[Tuple[int, ...]] |
| 1618 | + installed_packages = _get_installed_modules() |
| 1619 | + version = installed_packages.get(package) |
| 1620 | + if version is None: |
| 1621 | + return None |
| 1622 | + |
| 1623 | + return parse_version(version) |
| 1624 | + |
| 1625 | + |
1575 | 1626 | if PY37:
|
1576 | 1627 |
|
1577 | 1628 | def nanosecond_time():
|
|
0 commit comments