123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- """Meta related things."""
- from collections import namedtuple
- import re
- 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: int, minor: int, micro: int, release: str = "final",
- pre: int = 0, post: int = 0, dev: int = 0
- ) -> "Version":
- """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(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev)
- def _is_pre(self) -> bool:
- """Is prerelease."""
- return bool(self.pre > 0)
- def _is_dev(self) -> bool:
- """Is development."""
- return bool(self.release < "alpha")
- def _is_post(self) -> bool:
- """Is post."""
- return bool(self.post > 0)
- def _get_dev_status(self) -> str: # pragma: no cover
- """Get development status string."""
- return DEV_STATUS[self.release]
- def _get_canonical(self) -> str:
- """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: str) -> Version:
- """Parse version into a comparable Version tuple."""
- m = RE_VER.match(ver)
- if m is None:
- raise ValueError("'{}' is not a valid version".format(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)
- __version_info__ = Version(2, 3, 2, "final", post=1)
- __version__ = __version_info__._get_canonical()
|