|
112 | 112 | _namespace_packages = None
|
113 | 113 |
|
114 | 114 |
|
| 115 | +_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I) |
| 116 | + |
| 117 | + |
115 | 118 | class PEP440Warning(RuntimeWarning):
|
116 | 119 | """
|
117 | 120 | Used when there is an issue with a version or specifier not complying with
|
@@ -1389,6 +1392,38 @@ def safe_version(version):
|
1389 | 1392 | return re.sub('[^A-Za-z0-9.]+', '-', version)
|
1390 | 1393 |
|
1391 | 1394 |
|
| 1395 | +def _forgiving_version(version): |
| 1396 | + """Fallback when ``safe_version`` is not safe enough |
| 1397 | + >>> parse_version(_forgiving_version('0.23ubuntu1')) |
| 1398 | + <Version('0.23.dev0+sanitized.ubuntu1')> |
| 1399 | + >>> parse_version(_forgiving_version('0.23-')) |
| 1400 | + <Version('0.23.dev0+sanitized')> |
| 1401 | + >>> parse_version(_forgiving_version('0.-_')) |
| 1402 | + <Version('0.dev0+sanitized')> |
| 1403 | + >>> parse_version(_forgiving_version('42.+?1')) |
| 1404 | + <Version('42.dev0+sanitized.1')> |
| 1405 | + >>> parse_version(_forgiving_version('hello world')) |
| 1406 | + <Version('0.dev0+sanitized.hello.world')> |
| 1407 | + """ |
| 1408 | + version = version.replace(' ', '.') |
| 1409 | + match = _PEP440_FALLBACK.search(version) |
| 1410 | + if match: |
| 1411 | + safe = match["safe"] |
| 1412 | + rest = version[len(safe):] |
| 1413 | + else: |
| 1414 | + safe = "0" |
| 1415 | + rest = version |
| 1416 | + local = f"sanitized.{_safe_segment(rest)}".strip(".") |
| 1417 | + return f"{safe}.dev0+{local}" |
| 1418 | + |
| 1419 | + |
| 1420 | +def _safe_segment(segment): |
| 1421 | + """Convert an arbitrary string into a safe segment""" |
| 1422 | + segment = re.sub('[^A-Za-z0-9.]+', '-', segment) |
| 1423 | + segment = re.sub('-[^A-Za-z0-9]+', '-', segment) |
| 1424 | + return re.sub(r'\.[^A-Za-z0-9]+', '.', segment).strip(".-") |
| 1425 | + |
| 1426 | + |
1392 | 1427 | def safe_extra(extra):
|
1393 | 1428 | """Convert an arbitrary string to a standard 'extra' name
|
1394 | 1429 |
|
@@ -2637,7 +2672,7 @@ def _reload_version(self):
|
2637 | 2672 | @property
|
2638 | 2673 | def hashcmp(self):
|
2639 | 2674 | return (
|
2640 |
| - self.parsed_version, |
| 2675 | + self._forgiving_parsed_version, |
2641 | 2676 | self.precedence,
|
2642 | 2677 | self.key,
|
2643 | 2678 | self.location,
|
@@ -2695,6 +2730,32 @@ def parsed_version(self):
|
2695 | 2730 |
|
2696 | 2731 | return self._parsed_version
|
2697 | 2732 |
|
| 2733 | + @property |
| 2734 | + def _forgiving_parsed_version(self): |
| 2735 | + try: |
| 2736 | + return self.parsed_version |
| 2737 | + except packaging.version.InvalidVersion as ex: |
| 2738 | + self._parsed_version = parse_version(_forgiving_version(self.version)) |
| 2739 | + |
| 2740 | + notes = "\n".join(getattr(ex, "__notes__", [])) # PEP 678 |
| 2741 | + msg = f"""!!\n\n |
| 2742 | + ************************************************************************* |
| 2743 | + Invalid Version: {str(ex)}\n{notes} |
| 2744 | +
|
| 2745 | + This is a long overdue deprecation. |
| 2746 | + For the time being, `pkg_resources` will use `{self._parsed_version}` |
| 2747 | + as a replacement to avoid breaking existing environments, |
| 2748 | + but no future compatibility is guaranteed. |
| 2749 | +
|
| 2750 | + If you maintain package {self.project_name} you should implement |
| 2751 | + the relevant changes to adequate the project to PEP 440 immediately. |
| 2752 | + ************************************************************************* |
| 2753 | + \n\n!! |
| 2754 | + """ |
| 2755 | + warnings.warn(msg, DeprecationWarning) |
| 2756 | + |
| 2757 | + return self._parsed_version |
| 2758 | + |
2698 | 2759 | @property
|
2699 | 2760 | def version(self):
|
2700 | 2761 | try:
|
|
0 commit comments