From 163f0c8587b86f7e4b61eadc666935823404d89e Mon Sep 17 00:00:00 2001 From: allegroai <> Date: Fri, 22 May 2020 10:53:13 +0300 Subject: [PATCH] Add utilities.attrs using attr<19.2 flags, supporting attr>=19.2 with no deprecation warnings --- trains/backend_interface/metrics/events.py | 3 +- trains/utilities/attrs.py | 18 ++ trains/utilities/check_updates.py | 297 +------------------- trains/utilities/version.py | 299 +++++++++++++++++++++ 4 files changed, 322 insertions(+), 295 deletions(-) create mode 100644 trains/utilities/attrs.py create mode 100644 trains/utilities/version.py diff --git a/trains/backend_interface/metrics/events.py b/trains/backend_interface/metrics/events.py index 5400f940..689e8e57 100644 --- a/trains/backend_interface/metrics/events.py +++ b/trains/backend_interface/metrics/events.py @@ -12,6 +12,7 @@ from six.moves.urllib.parse import urlparse, urlunparse from ...backend_api.services import events from ...config import config from ...storage.util import quote_url +from ...utilities.attrs import attrs @six.add_metaclass(abc.ABCMeta) @@ -24,7 +25,7 @@ class MetricsEventAdapter(object): _default_nan_value = 0. """ Default value used when a np.nan value is encountered """ - @attr.attrs(eq=False, order=False, slots=True) + @attrs(cmp=False, slots=True) class FileEntry(object): """ File entry used to report on file data that needs to be uploaded prior to sending the event """ diff --git a/trains/utilities/attrs.py b/trains/utilities/attrs.py new file mode 100644 index 00000000..0ecee840 --- /dev/null +++ b/trains/utilities/attrs.py @@ -0,0 +1,18 @@ +import attr + +from .version import Version + + +class attrs(object): + def __init__(self, *args, **kwargs): + if any(x in kwargs for x in ("eq", "order")): + raise RuntimeError("Only `cmp` is supported for attr.attrs, not `eq` or `order`") + if Version(attr.__version__) >= Version("19.2"): + cmp = kwargs.pop("cmp", None) + if cmp is not None: + kwargs["eq"] = kwargs["order"] = cmp + self.args = args + self.kwargs = kwargs + + def __call__(self, f): + return attr.attrs(*self.args, **self.kwargs)(f) diff --git a/trains/utilities/check_updates.py b/trains/utilities/check_updates.py index 25133fa4..dc4a02a1 100644 --- a/trains/utilities/check_updates.py +++ b/trains/utilities/check_updates.py @@ -1,305 +1,14 @@ from __future__ import absolute_import, division, print_function -import collections import json -import re +import os import requests -import six -if six.PY3: - from math import inf -else: - inf = float('inf') from ..backend_api.session import Session +from ..backend_config import EnvEntry - -class InvalidVersion(ValueError): - """ - An invalid version was found, users should refer to PEP 440. - """ - - -_Version = collections.namedtuple( - "_Version", ["epoch", "release", "dev", "pre", "post", "local"] -) - - -class _BaseVersion(object): - def __init__(self, key): - self._key = key - - def __hash__(self): - return hash(self._key) - - def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) - - def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) - - def __eq__(self, other): - return self._compare(other, lambda s, o: s == o) - - def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) - - def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) - - def __ne__(self, other): - return self._compare(other, lambda s, o: s != o) - - def _compare(self, other, method): - if not isinstance(other, _BaseVersion): - return NotImplemented - - return method(self._key, other._key) - - -class Version(_BaseVersion): - VERSION_PATTERN = r""" - v? - (?: - (?:(?P[0-9]+)!)? # epoch - (?P[0-9]+(?:\.[0-9]+)*) # release segment - (?P
                                          # pre-release
-                [-_\.]?
-                (?P(a|b|c|rc|alpha|beta|pre|preview))
-                [-_\.]?
-                (?P[0-9]+)?
-            )?
-            (?P                                         # post release
-                (?:-(?P[0-9]+))
-                |
-                (?:
-                    [-_\.]?
-                    (?Ppost|rev|r)
-                    [-_\.]?
-                    (?P[0-9]+)?
-                )
-            )?
-            (?P                                          # dev release
-                [-_\.]?
-                (?Pdev)
-                [-_\.]?
-                (?P[0-9]+)?
-            )?
-        )
-        (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
-    """
-    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
-    _local_version_separators = re.compile(r"[\._-]")
-
-    def __init__(self, version):
-        # Validate the version and parse it into pieces
-        match = self._regex.search(version)
-        if not match:
-            raise InvalidVersion("Invalid version: '{0}'".format(version))
-
-        # Store the parsed out pieces of the version
-        self._version = _Version(
-            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
-            release=tuple(int(i) for i in match.group("release").split(".")),
-            pre=self._parse_letter_version(match.group("pre_l"), match.group("pre_n")),
-            post=self._parse_letter_version(
-                match.group("post_l") or '', match.group("post_n1") or match.group("post_n2") or ''
-            ),
-            dev=self._parse_letter_version(match.group("dev_l") or '', match.group("dev_n") or ''),
-            local=self._parse_local_version(match.group("local") or ''),
-        )
-
-        # Generate a key which will be used for sorting
-        key = self._cmpkey(
-            self._version.epoch,
-            self._version.release,
-            self._version.pre,
-            self._version.post,
-            self._version.dev,
-            self._version.local,
-        )
-
-        super(Version, self).__init__(key)
-
-    def __repr__(self):
-        return "".format(repr(str(self)))
-
-    def __str__(self):
-        parts = []
-
-        # Epoch
-        if self.epoch != 0:
-            parts.append("{0}!".format(self.epoch))
-
-        # Release segment
-        parts.append(".".join(str(x) for x in self.release))
-
-        # Pre-release
-        if self.pre is not None:
-            parts.append("".join(str(x) for x in self.pre))
-
-        # Post-release
-        if self.post is not None:
-            parts.append(".post{0}".format(self.post))
-
-        # Development release
-        if self.dev is not None:
-            parts.append(".dev{0}".format(self.dev))
-
-        # Local version segment
-        if self.local is not None:
-            parts.append("+{0}".format(self.local))
-
-        return "".join(parts)
-
-    @property
-    def epoch(self):
-        return self._version.epoch
-
-    @property
-    def release(self):
-        return self._version.release
-
-    @property
-    def pre(self):
-        return self._version.pre
-
-    @property
-    def post(self):
-        return self._version.post[1] if self._version.post else None
-
-    @property
-    def dev(self):
-        return self._version.dev[1] if self._version.dev else None
-
-    @property
-    def local(self):
-        if self._version.local:
-            return ".".join(str(x) for x in self._version.local)
-        else:
-            return None
-
-    @property
-    def public(self):
-        return str(self).split("+", 1)[0]
-
-    @property
-    def base_version(self):
-        parts = []
-
-        # Epoch
-        if self.epoch != 0:
-            parts.append("{0}!".format(self.epoch))
-
-        # Release segment
-        parts.append(".".join(str(x) for x in self.release))
-
-        return "".join(parts)
-
-    @property
-    def is_prerelease(self):
-        return self.dev is not None or self.pre is not None
-
-    @property
-    def is_postrelease(self):
-        return self.post is not None
-
-    @property
-    def is_devrelease(self):
-        return self.dev is not None
-
-    @staticmethod
-    def _parse_letter_version(letter, number):
-        if not letter and not number:
-            return None
-        if letter:
-            # We consider there to be an implicit 0 in a pre-release if there is
-            # not a numeral associated with it.
-            if number is None:
-                number = 0
-
-            # We normalize any letters to their lower case form
-            letter = letter.lower()
-
-            # We consider some words to be alternate spellings of other words and
-            # in those cases we want to normalize the spellings to our preferred
-            # spelling.
-            if letter == "alpha":
-                letter = "a"
-            elif letter == "beta":
-                letter = "b"
-            elif letter in ["c", "pre", "preview"]:
-                letter = "rc"
-            elif letter in ["rev", "r"]:
-                letter = "post"
-
-            return letter, int(number)
-        if not letter and number:
-            # We assume if we are given a number, but we are not given a letter
-            # then this is using the implicit post release syntax (e.g. 1.0-1)
-            letter = "post"
-
-        return letter, int(number)
-
-    @classmethod
-    def _parse_local_version(cls, local):
-        """
-        Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
-        """
-        if local is not None:
-            local = tuple(
-                part.lower() if not part.isdigit() else int(part)
-                for part in cls._local_version_separators.split(local)
-            )
-            if not local or not local[0]:
-                return None
-            return local
-        return None
-
-    @staticmethod
-    def _cmpkey(epoch, release, pre, post, dev, local):
-        # When we compare a release version, we want to compare it with all of the
-        # trailing zeros removed. So we'll use a reverse the list, drop all the now
-        # leading zeros until we come to something non zero, then take the rest
-        # re-reverse it back into the correct order and make it a tuple and use
-        # that for our sorting key.
-        # release = tuple(
-        #     reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
-        # )
-
-        # Versions without a pre-release (except as noted above) should sort after
-        # those with one.
-        if not pre:
-            pre = inf
-        elif pre:
-            pre = pre[1]
-
-        # Versions without a post segment should sort before those with one.
-        if not post:
-            post = -inf
-        else:
-            post = post[1]
-
-        # Versions without a development segment should sort after those with one.
-        if not dev:
-            dev = inf
-        else:
-            dev = dev[1]
-
-        if not local:
-            # Versions without a local segment should sort before those with one.
-            local = inf
-        else:
-            # Versions with a local segment need that segment parsed to implement
-            # the sorting rules in PEP440.
-            # - Alpha numeric segments sort before numeric segments
-            # - Alpha numeric segments sort lexicographically
-            # - Numeric segments sort numerically
-            # - Shorter versions sort before longer versions when the prefixes
-            #   match exactly
-            local = local[1]
-
-        return epoch, release, pre, post, dev, local
+from .version import Version
 
 
 class CheckPackageUpdates(object):
diff --git a/trains/utilities/version.py b/trains/utilities/version.py
new file mode 100644
index 00000000..7794d869
--- /dev/null
+++ b/trains/utilities/version.py
@@ -0,0 +1,299 @@
+from __future__ import absolute_import, division, print_function
+
+import collections
+import re
+
+import six
+
+if six.PY3:
+    from math import inf
+else:
+    inf = float('inf')
+
+
+class InvalidVersion(ValueError):
+    """
+    An invalid version was found, users should refer to PEP 440.
+    """
+
+
+_Version = collections.namedtuple(
+    "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
+)
+
+
+class _BaseVersion(object):
+    def __init__(self, key):
+        self._key = key
+
+    def __hash__(self):
+        return hash(self._key)
+
+    def __lt__(self, other):
+        return self._compare(other, lambda s, o: s < o)
+
+    def __le__(self, other):
+        return self._compare(other, lambda s, o: s <= o)
+
+    def __eq__(self, other):
+        return self._compare(other, lambda s, o: s == o)
+
+    def __ge__(self, other):
+        return self._compare(other, lambda s, o: s >= o)
+
+    def __gt__(self, other):
+        return self._compare(other, lambda s, o: s > o)
+
+    def __ne__(self, other):
+        return self._compare(other, lambda s, o: s != o)
+
+    def _compare(self, other, method):
+        if not isinstance(other, _BaseVersion):
+            return NotImplemented
+
+        return method(self._key, other._key)
+
+
+class Version(_BaseVersion):
+    VERSION_PATTERN = r"""
+        v?
+        (?:
+            (?:(?P[0-9]+)!)?                           # epoch
+            (?P[0-9]+(?:\.[0-9]+)*)                  # release segment
+            (?P
                                          # pre-release
+                [-_\.]?
+                (?P(a|b|c|rc|alpha|beta|pre|preview))
+                [-_\.]?
+                (?P[0-9]+)?
+            )?
+            (?P                                         # post release
+                (?:-(?P[0-9]+))
+                |
+                (?:
+                    [-_\.]?
+                    (?Ppost|rev|r)
+                    [-_\.]?
+                    (?P[0-9]+)?
+                )
+            )?
+            (?P                                          # dev release
+                [-_\.]?
+                (?Pdev)
+                [-_\.]?
+                (?P[0-9]+)?
+            )?
+        )
+        (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+    """
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+    _local_version_separators = re.compile(r"[\._-]")
+
+    def __init__(self, version):
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion("Invalid version: '{0}'".format(version))
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=self._parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=self._parse_letter_version(
+                match.group("post_l") or '', match.group("post_n1") or match.group("post_n2") or ''
+            ),
+            dev=self._parse_letter_version(match.group("dev_l") or '', match.group("dev_n") or ''),
+            local=self._parse_local_version(match.group("local") or ''),
+        )
+
+        # Generate a key which will be used for sorting
+        key = self._cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+        super(Version, self).__init__(key)
+
+    def __repr__(self):
+        return "".format(repr(str(self)))
+
+    def __str__(self):
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append("{0}!".format(self.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(".post{0}".format(self.post))
+
+        # Development release
+        if self.dev is not None:
+            parts.append(".dev{0}".format(self.dev))
+
+        # Local version segment
+        if self.local is not None:
+            parts.append("+{0}".format(self.local))
+
+        return "".join(parts)
+
+    @property
+    def epoch(self):
+        return self._version.epoch
+
+    @property
+    def release(self):
+        return self._version.release
+
+    @property
+    def pre(self):
+        return self._version.pre
+
+    @property
+    def post(self):
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self):
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self):
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self):
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self):
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append("{0}!".format(self.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self):
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self):
+        return self.post is not None
+
+    @property
+    def is_devrelease(self):
+        return self.dev is not None
+
+    @staticmethod
+    def _parse_letter_version(letter, number):
+        if not letter and not number:
+            return None
+        if letter:
+            # We consider there to be an implicit 0 in a pre-release if there is
+            # not a numeral associated with it.
+            if number is None:
+                number = 0
+
+            # We normalize any letters to their lower case form
+            letter = letter.lower()
+
+            # We consider some words to be alternate spellings of other words and
+            # in those cases we want to normalize the spellings to our preferred
+            # spelling.
+            if letter == "alpha":
+                letter = "a"
+            elif letter == "beta":
+                letter = "b"
+            elif letter in ["c", "pre", "preview"]:
+                letter = "rc"
+            elif letter in ["rev", "r"]:
+                letter = "post"
+
+            return letter, int(number)
+        if not letter and number:
+            # We assume if we are given a number, but we are not given a letter
+            # then this is using the implicit post release syntax (e.g. 1.0-1)
+            letter = "post"
+
+        return letter, int(number)
+
+    @classmethod
+    def _parse_local_version(cls, local):
+        """
+        Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+        """
+        if local is not None:
+            local = tuple(
+                part.lower() if not part.isdigit() else int(part)
+                for part in cls._local_version_separators.split(local)
+            )
+            if not local or not local[0]:
+                return None
+            return local
+        return None
+
+    @staticmethod
+    def _cmpkey(epoch, release, pre, post, dev, local):
+        # When we compare a release version, we want to compare it with all of the
+        # trailing zeros removed. So we'll use a reverse the list, drop all the now
+        # leading zeros until we come to something non zero, then take the rest
+        # re-reverse it back into the correct order and make it a tuple and use
+        # that for our sorting key.
+        # release = tuple(
+        #     reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+        # )
+
+        # Versions without a pre-release (except as noted above) should sort after
+        # those with one.
+        if not pre:
+            pre = inf
+        elif pre:
+            pre = pre[1]
+
+        # Versions without a post segment should sort before those with one.
+        if not post:
+            post = -inf
+        else:
+            post = post[1]
+
+        # Versions without a development segment should sort after those with one.
+        if not dev:
+            dev = inf
+        else:
+            dev = dev[1]
+
+        if not local:
+            # Versions without a local segment should sort before those with one.
+            local = inf
+        else:
+            # Versions with a local segment need that segment parsed to implement
+            # the sorting rules in PEP440.
+            # - Alpha numeric segments sort before numeric segments
+            # - Alpha numeric segments sort lexicographically
+            # - Numeric segments sort numerically
+            # - Shorter versions sort before longer versions when the prefixes
+            #   match exactly
+            local = local[1]
+
+        return epoch, release, pre, post, dev, local
\ No newline at end of file