| """ |
| Backport of PEP 562. |
| |
| https://pypi.org/search/?q=pep562 |
| |
| Licensed under MIT |
| Copyright (c) 2018 Isaac Muse <isaacmuse@gmail.com> |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
| documentation files (the "Software"), to deal in the Software without restriction, including without limitation |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, |
| and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all copies or substantial portions |
| of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
| TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| IN THE SOFTWARE. |
| """ |
| import sys |
| from collections import namedtuple |
| import re |
| |
| __all__ = ('Pep562',) |
| |
| RE_VER = re.compile( |
| r'''(?x) |
| (?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<micro>\d+))? |
| (?:(?P<type>a|b|rc)(?P<pre>\d+))? |
| (?:\.post(?P<post>\d+))? |
| (?:\.dev(?P<dev>\d+))? |
| ''' |
| ) |
| |
| REL_MAP = { |
| ".dev": "", |
| ".dev-alpha": "a", |
| ".dev-beta": "b", |
| ".dev-candidate": "rc", |
| "alpha": "a", |
| "beta": "b", |
| "candidate": "rc", |
| "final": "" |
| } |
| |
| DEV_STATUS = { |
| ".dev": "2 - Pre-Alpha", |
| ".dev-alpha": "2 - Pre-Alpha", |
| ".dev-beta": "2 - Pre-Alpha", |
| ".dev-candidate": "2 - Pre-Alpha", |
| "alpha": "3 - Alpha", |
| "beta": "4 - Beta", |
| "candidate": "4 - Beta", |
| "final": "5 - Production/Stable" |
| } |
| |
| PRE_REL_MAP = {"a": 'alpha', "b": 'beta', "rc": 'candidate'} |
| |
| |
| class Version(namedtuple("Version", ["major", "minor", "micro", "release", "pre", "post", "dev"])): |
| """ |
| Get the version (PEP 440). |
| |
| A biased approach to the PEP 440 semantic version. |
| |
| Provides a tuple structure which is sorted for comparisons `v1 > v2` etc. |
| (major, minor, micro, release type, pre-release build, post-release build, development release build) |
| Release types are named in is such a way they are comparable with ease. |
| Accessors to check if a development, pre-release, or post-release build. Also provides accessor to get |
| development status for setup files. |
| |
| How it works (currently): |
| |
| - You must specify a release type as either `final`, `alpha`, `beta`, or `candidate`. |
| - To define a development release, you can use either `.dev`, `.dev-alpha`, `.dev-beta`, or `.dev-candidate`. |
| The dot is used to ensure all development specifiers are sorted before `alpha`. |
| You can specify a `dev` number for development builds, but do not have to as implicit development releases |
| are allowed. |
| - You must specify a `pre` value greater than zero if using a prerelease as this project (not PEP 440) does not |
| allow implicit prereleases. |
| - You can optionally set `post` to a value greater than zero to make the build a post release. While post releases |
| are technically allowed in prereleases, it is strongly discouraged, so we are rejecting them. It should be |
| noted that we do not allow `post0` even though PEP 440 does not restrict this. This project specifically |
| does not allow implicit post releases. |
| - It should be noted that we do not support epochs `1!` or local versions `+some-custom.version-1`. |
| |
| Acceptable version releases: |
| |
| ``` |
| Version(1, 0, 0, "final") 1.0 |
| Version(1, 2, 0, "final") 1.2 |
| Version(1, 2, 3, "final") 1.2.3 |
| Version(1, 2, 0, ".dev-alpha", pre=4) 1.2a4 |
| Version(1, 2, 0, ".dev-beta", pre=4) 1.2b4 |
| Version(1, 2, 0, ".dev-candidate", pre=4) 1.2rc4 |
| Version(1, 2, 0, "final", post=1) 1.2.post1 |
| Version(1, 2, 3, ".dev") 1.2.3.dev0 |
| Version(1, 2, 3, ".dev", dev=1) 1.2.3.dev1 |
| ``` |
| |
| """ |
| |
| def __new__(cls, major, minor, micro, release="final", pre=0, post=0, dev=0): |
| """Validate version info.""" |
| |
| # Ensure all parts are positive integers. |
| for value in (major, minor, micro, pre, post): |
| if not (isinstance(value, int) and value >= 0): |
| raise ValueError("All version parts except 'release' should be integers.") |
| |
| if release not in REL_MAP: |
| raise ValueError("'{}' is not a valid release type.".format(release)) |
| |
| # Ensure valid pre-release (we do not allow implicit pre-releases). |
| if ".dev-candidate" < release < "final": |
| if pre == 0: |
| raise ValueError("Implicit pre-releases not allowed.") |
| elif dev: |
| raise ValueError("Version is not a development release.") |
| elif post: |
| raise ValueError("Post-releases are not allowed with pre-releases.") |
| |
| # Ensure valid development or development/pre release |
| elif release < "alpha": |
| if release > ".dev" and pre == 0: |
| raise ValueError("Implicit pre-release not allowed.") |
| elif post: |
| raise ValueError("Post-releases are not allowed with pre-releases.") |
| |
| # Ensure a valid normal release |
| else: |
| if pre: |
| raise ValueError("Version is not a pre-release.") |
| elif dev: |
| raise ValueError("Version is not a development release.") |
| |
| return super().__new__(cls, major, minor, micro, release, pre, post, dev) |
| |
| def _is_pre(self): |
| """Is prerelease.""" |
| |
| return self.pre > 0 |
| |
| def _is_dev(self): |
| """Is development.""" |
| |
| return bool(self.release < "alpha") |
| |
| def _is_post(self): |
| """Is post.""" |
| |
| return self.post > 0 |
| |
| def _get_dev_status(self): # pragma: no cover |
| """Get development status string.""" |
| |
| return DEV_STATUS[self.release] |
| |
| def _get_canonical(self): |
| """Get the canonical output string.""" |
| |
| # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed.. |
| if self.micro == 0: |
| ver = "{}.{}".format(self.major, self.minor) |
| else: |
| ver = "{}.{}.{}".format(self.major, self.minor, self.micro) |
| if self._is_pre(): |
| ver += '{}{}'.format(REL_MAP[self.release], self.pre) |
| if self._is_post(): |
| ver += ".post{}".format(self.post) |
| if self._is_dev(): |
| ver += ".dev{}".format(self.dev) |
| |
| return ver |
| |
| |
| def parse_version(ver, pre=False): |
| """Parse version into a comparable Version tuple.""" |
| |
| m = RE_VER.match(ver) |
| |
| # Handle major, minor, micro |
| major = int(m.group('major')) |
| minor = int(m.group('minor')) if m.group('minor') else 0 |
| micro = int(m.group('micro')) if m.group('micro') else 0 |
| |
| # Handle pre releases |
| if m.group('type'): |
| release = PRE_REL_MAP[m.group('type')] |
| pre = int(m.group('pre')) |
| else: |
| release = "final" |
| pre = 0 |
| |
| # Handle development releases |
| dev = m.group('dev') if m.group('dev') else 0 |
| if m.group('dev'): |
| dev = int(m.group('dev')) |
| release = '.dev-' + release if pre else '.dev' |
| else: |
| dev = 0 |
| |
| # Handle post |
| post = int(m.group('post')) if m.group('post') else 0 |
| |
| return Version(major, minor, micro, release, pre, post, dev) |
| |
| |
| class Pep562: |
| """ |
| Backport of PEP 562 <https://pypi.org/search/?q=pep562>. |
| |
| Wraps the module in a class that exposes the mechanics to override `__dir__` and `__getattr__`. |
| The given module will be searched for overrides of `__dir__` and `__getattr__` and use them when needed. |
| """ |
| |
| def __init__(self, name): |
| """Acquire `__getattr__` and `__dir__`, but only replace module for versions less than Python 3.7.""" |
| |
| self._module = sys.modules[name] |
| self._get_attr = getattr(self._module, '__getattr__', None) |
| self._get_dir = getattr(self._module, '__dir__', None) |
| sys.modules[name] = self |
| |
| def __dir__(self): |
| """Return the overridden `dir` if one was provided, else apply `dir` to the module.""" |
| |
| return self._get_dir() if self._get_dir else dir(self._module) |
| |
| def __getattr__(self, name): |
| """Attempt to retrieve the attribute from the module, and if missing, use the overridden function if present.""" |
| |
| try: |
| return getattr(self._module, name) |
| except AttributeError: |
| if self._get_attr: |
| return self._get_attr(name) |
| raise |
| |
| |
| __version_info__ = Version(1, 0, 0, "final") |
| __version__ = __version_info__._get_canonical() |