Add posix property to get os release version in semver valid format

This commit is contained in:
Sonia Sharma 2021-06-15 15:50:03 -07:00 коммит произвёл Sonia Sharma
Родитель 4bd872562a
Коммит 8c22a15972
5 изменённых файлов: 483 добавлений и 14 удалений

Просмотреть файл

@ -4,7 +4,19 @@
import re
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Pattern, Type, Union
from typing import (
TYPE_CHECKING,
Any,
Dict,
Iterable,
List,
Optional,
Pattern,
Type,
Union,
)
from semver import VersionInfo
from lisa.base_tools.wget import Wget
from lisa.executable import Tool
@ -192,6 +204,18 @@ class Windows(OperatingSystem):
class Posix(OperatingSystem, BaseClassMixin):
BASEVERSION = re.compile(
r"""[vV]?
(?P<major>0|[1-9]\d*)
(\.\-\_
(?P<minor>0|[1-9]\d*)
(\.\-\_
(?P<patch>0|[1-9]\d*)
)?
)?""",
re.VERBOSE,
)
__os_info_pattern = re.compile(
r"^(?P<name>.*)=[\"\']?(?P<value>.*?)[\"\']?$", re.MULTILINE
)
@ -221,6 +245,46 @@ class Posix(OperatingSystem, BaseClassMixin):
def name_pattern(cls) -> Pattern[str]:
return re.compile(f"^{cls.type_name()}$")
@property
def release_version(self) -> VersionInfo:
release_version = self._get_os_version().release
if VersionInfo.isvalid(release_version):
return VersionInfo.parse(release_version)
return self._coerce_version(release_version)
def _coerce_version(self, version: str) -> VersionInfo:
"""
Convert an incomplete version string into a semver-compatible Version
object
source -
https://python-semver.readthedocs.io/en/latest/usage.html#dealing-with-invalid-versions
* Tries to detect a "basic" version string (``major.minor.patch``).
* If not enough components can be found, missing components are
set to zero to obtain a valid semver version.
:param str version: the version string to convert
:return: a tuple with a :class:`Version` instance (or ``None``
if it's not a version) and the rest of the string which doesn't
belong to a basic version.
:rtype: tuple(:class:`Version` | None, str)
"""
match = self.BASEVERSION.search(version)
if not match:
raise LisaException("The OS version release is not in a valid format")
ver: Dict[str, Any] = {
key: 0 if value is None else int(value)
for key, value in match.groupdict().items()
}
release_version = VersionInfo(**ver)
rest = match.string[match.end() :] # noqa:E203
release_version.build = rest
return release_version
def _install_packages(
self, packages: Union[List[str]], signed: bool = True
) -> None:

Просмотреть файл

@ -74,10 +74,7 @@ class LisDriver(Tool):
@property
def can_install(self) -> bool:
if (
isinstance(self.node.os, Redhat)
and float(self.node.os.os_version.release) < 7.8
):
if isinstance(self.node.os, Redhat) and self.node.os.release_version < "7.8.0":
return True
return False

24
poetry.lock сгенерированный
Просмотреть файл

@ -1000,6 +1000,14 @@ python-versions = "*"
[package.extras]
dev = ["pytest"]
[[package]]
name = "semver"
version = "2.13.0"
description = "Python helper for Semantic Versioning (http://semver.org/)"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "six"
version = "1.16.0"
@ -1128,7 +1136,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "070c27ea682c23c7592026a131c438e5b6aa6f13bb653f90a9bcd0977eb272ba"
content-hash = "6b3583b17fba0bb6f35a181a168476677ca3d8d2eef70ad66a5d921a01882972"
[metadata.files]
appdirs = [
@ -1313,6 +1321,7 @@ coverage = [
]
cryptography = [
{file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
{file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"},
@ -1338,6 +1347,7 @@ flake8 = [
]
flake8-black = [
{file = "flake8-black-0.2.1.tar.gz", hash = "sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"},
{file = "flake8_black-0.2.1-py3-none-any.whl", hash = "sha256:941514149cb8b489cb17a4bb1cf18d84375db3b34381bb018de83509437931a0"},
]
flake8-bugbear = [
{file = "flake8-bugbear-21.4.3.tar.gz", hash = "sha256:2346c81f889955b39e4a368eb7d508de723d9de05716c287dc860a4073dc57e7"},
@ -1578,26 +1588,18 @@ pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
{file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
{file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
{file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
{file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
{file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
{file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
{file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
{file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
{file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
{file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
{file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
@ -1661,6 +1663,10 @@ retry = [
rope = [
{file = "rope-0.18.0.tar.gz", hash = "sha256:786b5c38c530d4846aa68a42604f61b4e69a493390e3ca11b88df0fbfdc3ed04"},
]
semver = [
{file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"},
{file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},

Просмотреть файл

@ -26,6 +26,7 @@ python = "^3.8"
python-dateutil = "^2.8.1"
retry = "^0.9.2"
spurplus = "^2.3.4"
semver = "^2.13.0"
[tool.poetry.dev-dependencies]
black = "^20.8b1"

401
typings/semver/__init__.pyi Normal file
Просмотреть файл

@ -0,0 +1,401 @@
"""
This type stub file was generated by pyright.
"""
import argparse
from __future__ import print_function
from typing import (
Any,
Dict,
Generator,
List,
Literal,
Optional,
OrderedDict,
Tuple,
Union,
)
"""Python helper for Semantic Versioning (http://semver.org/)"""
PY2 = ...
PY3 = ...
__version__ = ...
__author__ = ...
__author_email__ = ...
__maintainer__ = ...
__maintainer_email__ = ...
SEMVER_SPEC_VERSION = ...
def comparator(operator: Any) -> Any: # -> (self: Unknown, other: Unknown) -> Unknown:
"""Wrap a VersionInfo binary op method in a type-check."""
...
class VersionInfo:
"""
A semver compatible version class.
:param int major: version when you make incompatible API changes.
:param int minor: version when you add functionality in
a backwards-compatible manner.
:param int patch: version when you make backwards-compatible bug fixes.
:param str prerelease: an optional prerelease string
:param str build: an optional build string
"""
def __init__(
self,
major: int,
minor: int = ...,
patch: int = ...,
prerelease: Optional[str] = ...,
build: Optional[str] = ...,
) -> None: ...
@property
def major(self) -> Any: # -> Unknown:
"""The major part of a version (read-only)."""
...
@major.setter
def major(self, value: int) -> Any: ...
@property
def minor(self) -> Any: # -> Unknown:
"""The minor part of a version (read-only)."""
...
@minor.setter
def minor(self, value: int) -> Any: ...
@property
def patch(self) -> Any: # -> Unknown:
"""The patch part of a version (read-only)."""
...
@patch.setter
def patch(self, value: int) -> Any: ...
@property
def prerelease(self) -> Union[str, None]: # -> str | None:
"""The prerelease part of a version (read-only)."""
...
@prerelease.setter
def prerelease(self, value: str) -> Union[str, None]: ...
@property
def build(self) -> Union[str, None]: # -> str | None:
"""The build part of a version (read-only)."""
...
@build.setter
def build(self, value: str) -> Union[str, None]: ...
def to_tuple(
self,
) -> Tuple[
Any, Any, Any, str | None, str | None
]: # -> tuple[Unknown, Unknown, Unknown, str | None, str | None]:
"""
Convert the VersionInfo object to a tuple.
.. versionadded:: 2.10.0
Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to
make this function available in the public API.
:return: a tuple with all the parts
:rtype: tuple
>>> semver.VersionInfo(5, 3, 1).to_tuple()
(5, 3, 1, None, None)
"""
...
def to_dict(
self,
) -> OrderedDict[
Literal["major", "minor", "patch", "prerelease", "build"], Any
]: # -> OrderedDict[Literal['major', 'minor', 'patch', 'prerelease', 'build'], Unknown]:
"""
Convert the VersionInfo object to an OrderedDict.
.. versionadded:: 2.10.0
Renamed ``VersionInfo._asdict`` to ``VersionInfo.to_dict`` to
make this function available in the public API.
:return: an OrderedDict with the keys in the order ``major``, ``minor``,
``patch``, ``prerelease``, and ``build``.
:rtype: :class:`collections.OrderedDict`
>>> semver.VersionInfo(3, 2, 1).to_dict()
OrderedDict([('major', 3), ('minor', 2), ('patch', 1), \
('prerelease', None), ('build', None)])
"""
...
def __iter__(
self,
) -> Generator[
Any | str | None, None, None
]: # -> Generator[Unknown | str | None, None, None]:
"""Implement iter(self)."""
...
def bump_major(self) -> VersionInfo: # -> VersionInfo:
"""
Raise the major part of the version, return a new object but leave self
untouched.
:return: new object with the raised major part
:rtype: :class:`VersionInfo`
>>> ver = semver.VersionInfo.parse("3.4.5")
>>> ver.bump_major()
VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None)
"""
...
def bump_minor(self) -> VersionInfo: # -> VersionInfo:
"""
Raise the minor part of the version, return a new object but leave self
untouched.
:return: new object with the raised minor part
:rtype: :class:`VersionInfo`
>>> ver = semver.VersionInfo.parse("3.4.5")
>>> ver.bump_minor()
VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None)
"""
...
def bump_patch(self) -> VersionInfo: # -> VersionInfo:
"""
Raise the patch part of the version, return a new object but leave self
untouched.
:return: new object with the raised patch part
:rtype: :class:`VersionInfo`
>>> ver = semver.VersionInfo.parse("3.4.5")
>>> ver.bump_patch()
VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None)
"""
...
def bump_prerelease(self, token: str = ...) -> VersionInfo: # -> VersionInfo:
"""
Raise the prerelease part of the version, return a new object but leave
self untouched.
:param token: defaults to 'rc'
:return: new object with the raised prerelease part
:rtype: :class:`VersionInfo`
>>> ver = semver.VersionInfo.parse("3.4.5-rc.1")
>>> ver.bump_prerelease()
VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \
build=None)
"""
...
def bump_build(self, token: str = ...) -> VersionInfo: # -> VersionInfo:
"""
Raise the build part of the version, return a new object but leave self
untouched.
:param token: defaults to 'build'
:return: new object with the raised build part
:rtype: :class:`VersionInfo`
>>> ver = semver.VersionInfo.parse("3.4.5-rc.1+build.9")
>>> ver.bump_build()
VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \
build='build.10')
"""
...
def compare(
self,
other: Union[
str,
Dict[str, Union[int, str]],
Tuple[str, Union[int, str]],
List[str],
VersionInfo,
],
) -> int: # -> int:
"""
Compare self with other.
:param other: the second version (can be string, a dict, tuple/list, or
a VersionInfo instance)
:return: The return value is negative if ver1 < ver2,
zero if ver1 == ver2 and strictly positive if ver1 > ver2
:rtype: int
>>> semver.VersionInfo.parse("1.0.0").compare("2.0.0")
-1
>>> semver.VersionInfo.parse("2.0.0").compare("1.0.0")
1
>>> semver.VersionInfo.parse("2.0.0").compare("2.0.0")
0
>>> semver.VersionInfo.parse("2.0.0").compare(dict(major=2, minor=0, patch=0))
0
"""
...
def next_version(
self,
part: Literal["major", "minor", "patch", "prerelease", "build"],
prerelease_token: str = ...,
) -> Union[Any, VersionInfo]: # -> VersionInfo | Any:
"""
Determines next version, preserving natural order.
.. versionadded:: 2.10.0
This function is taking prereleases into account.
The "major", "minor", and "patch" raises the respective parts like
the ``bump_*`` functions. The real difference is using the
"preprelease" part. It gives you the next patch version of the prerelease,
for example:
>>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease"))
'0.1.5-rc.1'
:param part: One of "major", "minor", "patch", or "prerelease"
:param prerelease_token: prefix string of prerelease, defaults to 'rc'
:return: new object with the appropriate part raised
:rtype: :class:`VersionInfo`
"""
...
@comparator
def __eq__(self, other: Any) -> bool: ...
@comparator
def __ne__(self, other: Any) -> bool: ...
@comparator
def __lt__(self, other: Any) -> bool: ...
@comparator
def __le__(self, other: Any) -> bool: ...
@comparator
def __gt__(self, other: Any) -> bool: ...
@comparator
def __ge__(self, other: Any) -> bool: ...
def __getitem__(
self, index: Union[int, slice]
) -> Union[
Any, str, tuple[Any, str, None]
]: # -> Unknown | str | tuple[Unknown | str | None, ...] | None:
"""
self.__getitem__(index) <==> self[index]
Implement getitem. If the part requested is undefined, or a part of the
range requested is undefined, it will throw an index error.
Negative indices are not supported
:param Union[int, slice] index: a positive integer indicating the
offset or a :func:`slice` object
:raises: IndexError, if index is beyond the range or a part is None
:return: the requested part of the version at position index
>>> ver = semver.VersionInfo.parse("3.4.5")
>>> ver[0], ver[1], ver[2]
(3, 4, 5)
"""
...
def __repr__(self) -> str: ...
def __str__(self) -> str:
"""str(self)"""
...
def __hash__(self) -> int: ...
def finalize_version(self) -> VersionInfo: # -> VersionInfo:
"""
Remove any prerelease and build metadata from the version.
:return: a new instance with the finalized version string
:rtype: :class:`VersionInfo`
>>> str(semver.VersionInfo.parse('1.2.3-rc.5').finalize_version())
'1.2.3'
"""
...
def match(self, match_expr: str) -> bool: # -> bool:
"""
Compare self to match a match expression.
:param str match_expr: operator and version; valid operators are
< smaller than
> greater than
>= greator or equal than
<= smaller or equal than
== equal
!= not equal
:return: True if the expression matches the version, otherwise False
:rtype: bool
>>> semver.VersionInfo.parse("2.0.0").match(">=1.0.0")
True
>>> semver.VersionInfo.parse("1.0.0").match(">1.0.0")
False
"""
...
@classmethod
def parse(cls, version: str) -> VersionInfo: # -> VersionInfo:
"""
Parse version string to a VersionInfo instance.
:param version: version string
:return: a :class:`VersionInfo` instance
:raises: :class:`ValueError`
:rtype: :class:`VersionInfo`
.. versionchanged:: 2.11.0
Changed method from static to classmethod to
allow subclasses.
>>> semver.VersionInfo.parse('3.4.5-pre.2+build.4')
VersionInfo(major=3, minor=4, patch=5, \
prerelease='pre.2', build='build.4')
"""
...
def replace(self, **parts: Union[int, str, None]) -> VersionInfo: # -> VersionInfo:
"""
Replace one or more parts of a version and return a new
:class:`VersionInfo` object, but leave self untouched
.. versionadded:: 2.9.0
Added :func:`VersionInfo.replace`
:param dict parts: the parts to be updated. Valid keys are:
``major``, ``minor``, ``patch``, ``prerelease``, or ``build``
:return: the new :class:`VersionInfo` object with the changed
parts
:raises: :class:`TypeError`, if ``parts`` contains invalid keys
"""
...
@classmethod
def isvalid(cls, version: str) -> bool: # -> bool:
"""
Check if the string is a valid semver version.
.. versionadded:: 2.9.1
:param str version: the version string to check
:return: True if the version string is a valid semver version, False
otherwise.
:rtype: bool
"""
...
def cmd_bump(args: argparse.Namespace) -> str: # -> str:
"""
Subcommand: Bumps a version.
Synopsis: bump <PART> <VERSION>
<PART> can be major, minor, patch, prerelease, or build
:param args: The parsed arguments
:type args: :class:`argparse.Namespace`
:return: the new, bumped version
"""
...
def cmd_check(args: argparse.Namespace) -> None: # -> None:
"""
Subcommand: Checks if a string is a valid semver version.
Synopsis: check <VERSION>
:param args: The parsed arguments
:type args: :class:`argparse.Namespace`
"""
...
def cmd_compare(args: argparse.Namespace) -> str: # -> str:
"""
Subcommand: Compare two versions
Synopsis: compare <VERSION1> <VERSION2>
:param args: The parsed arguments
:type args: :class:`argparse.Namespace`
"""
...
def cmd_nextver(args: argparse.Namespace) -> str: # -> str:
"""
Subcommand: Determines the next version, taking prereleases into account.
Synopsis: nextver <VERSION> <PART>
:param args: The parsed arguments
:type args: :class:`argparse.Namespace`
"""
...
def createparser() -> argparse.ArgumentParser: # -> ArgumentParser:
"""
Create an :class:`argparse.ArgumentParser` instance.
:return: parser instance
:rtype: :class:`argparse.ArgumentParser`
"""
...
def process(args: argparse.Namespace) -> str:
"""
Process the input from the CLI.
:param args: The parsed arguments
:type args: :class:`argparse.Namespace`
:param parser: the parser instance
:type parser: :class:`argparse.ArgumentParser`
:return: result of the selected action
:rtype: str
"""
...
def main(cliargs: Any = ...) -> Literal[0, 2]: # -> Literal[0, 2]:
"""
Entry point for the application script.
:param list cliargs: Arguments to parse or None (=use :class:`sys.argv`)
:return: error code
:rtype: int
"""
...
if __name__ == "__main__": ...