from enum import Enum
from hashlib import md5
from string import ascii_lowercase
from typing import List, Optional, Union
from .error import IncorrectAlphanumeric, TooManyAuthors
__all__ = (
"VersionAuthor",
"VersionAlphanumericType",
"Version",
)
[docs]class VersionAlphanumericType(str, Enum):
ALPHA = "alpha"
BETA = "beta"
RELEASE_CANDIDATE = "rc"
LETTER = ascii_lowercase
[docs]class VersionAuthor:
"""
A class representing the author involved in a version.
:ivar MD5Hash _hash: The hashed representation of the class.
:ivar bool _co_author: Whether this is a co-author or not.
:ivar bool active: Whether the author is active or not.
:ivar str email: The email of the author.
:ivar str name: The name of the author.
"""
__slots__ = ("_hash", "_co_author", "active", "email", "name")
def __init__(
self,
name,
*,
shared: Optional[bool] = False,
active: Optional[bool] = True,
email: Optional[str] = None,
) -> None:
"""
:param name: The name of the author.
:type name: str
:param shared?: The author's relationship as the main or co-author. Defaults to ``False``.
:type shared?: Optional[bool]
:param active?: The author's state of activity. Defaults to ``True``.
:type active?: Optional[bool]
:param email?: The author's email address or point of contact. Defaults to ``None``.
:type email?: Optional[str]
"""
self.name = name
self._co_author = shared
self.active = active
self.email = email
self._hash = md5(self.__str__().encode())
def __hash__(self):
return self._hash
def __str__(self) -> str:
return f'{self.name}{f" <{self.email}>" if self.email else ""}'
@property
def is_co_author(self) -> bool:
"""Returns whether the author is a co-author or not."""
return self._co_author
@property
def signature(self) -> str:
return f"{'Co-authored by: ' if self._co_author else ''}{self.__str__()}"
[docs]class Version:
"""
A class representing how a version is structured for a 3rd party library.
.. note::
This class respects the design and application of Semantic
Versioning 2.0.0, (SemVer) a widely accepted standardisation
of version control for modules and projects.
:ivar int _major: The major version.
:ivar int _minor: The minor version.
:ivar int _patch: The patch version.
:ivar List[VersionAuthor] _authors: The authors tied to the version release.
:ivar str __version: The representation of the version.
:ivar Optional[Dict[str, Union[int, VersionAlphanumericType]]] __alphanum: The alphanumeric typing of the version.
"""
__slots__ = ("_major", "_minor", "_patch", "_authors", "__version", "__alphanum")
def __init__(self, **kwargs) -> None:
"""
:param major?: The major version. If not specified, ``version`` will be read from.
:type major?: Optional[Union[str, int]]
:param minor?: The minor version. If not specified, ``version`` will be read from.
:type minor?: Optional[Union[str, int]]
:param patch?: The patch version. If not specified, ``version`` will be read from.
:type patch?: Optional[Union[str, int]]
:param version?: The overall version. Must be used if ``major``, ``minor`` or ``patch`` are not.
:type version?: Optional[str]
"""
self._major = int(kwargs.get("major") or kwargs.get("version", "0.0.0").split(".")[0])
self._minor = int(kwargs.get("minor") or kwargs.get("version", "0.0.0").split(".")[1])
self._patch = int(kwargs.get("patch") or kwargs.get("version", "0.0.0").split(".")[2])
self._authors = kwargs.get("authors", []) or kwargs.get("author", [])
self.__version = f"{self._major}.{self._minor}.{self._patch}"
self.__alphanum = None
def __repr__(self) -> str:
return self.__version
def __str__(self) -> str:
return self.__repr__()
@property
def major(self) -> int:
"""Returns the major version."""
return self._major
@property
def minor(self) -> int:
"""Returns the minor version."""
return self._minor
@property
def patch(self) -> int:
"""Returns the patch version."""
return self._patch
@property
def author(self) -> Optional[Union[Exception, VersionAuthor]]:
"""
Returns the author of the version.
If multiple authors exist, it will choose the only one that is not a co-author.
:return: The author of the version, if one exists.
:rtype: Optional[VersionAuthor]
:raises TooManyAuthors: Too many main authors were found.
"""
_author: str = ""
if len(self._authors) == 1:
return self._authors
elif len(self._authors) > 1:
amount: int = 0
for author in self._authors:
if author.co_author:
amount += 1
elif amount > 1:
raise TooManyAuthors
else:
_author = author
continue
return _author
else:
return None
@property
def authors(self) -> Optional[List[VersionAuthor]]:
"""
Returns the list of authors under the version.
:return: The authors of the version, if any exist.
:rtype: Optional[List[VersionAuthor]]
"""
if len(self._authors) > 1:
return self._authors
elif not len(self._authors):
return None
@property
def is_alphanumeric(self) -> bool:
"""Returns whether the version is alphanumeric or not."""
return bool(self.__alphanum)
[docs] @classmethod
def extend_version(cls, **kwargs) -> Union[Exception, str]:
r"""
Allows the version to be extended upon with an alphanumeric format.
:param \**kwargs: Key-word arguments to be supplied as ``alpha``, ``beta`` or ``rc`` respectively.
:type \**kwargs: Dict[VersionAlphanumericType, int]
:return: The new version with the alphanumeric.
:rtype: str
:raises IncorrectAlphanumeric: The alphanumeric version was incorrectly formatted.
"""
if "-" not in cls.__version:
identifiers: tuple = (
VersionAlphanumericType.ALPHA,
VersionAlphanumericType.BETA,
VersionAlphanumericType.RELEASE_CANDIDATE,
)
amount: int = 0
for key, value in kwargs.items():
if key in identifiers and len(value) == 1:
amount += 1
cls.__version = f"{cls.__version}-{key}.{value}"
cls.__alphanum = {"type": key, "identifier": int(value)}
elif key in identifiers or amount > 1:
raise IncorrectAlphanumeric
else:
continue
return cls.__version