"""The functions in this module are used to validate schemas with the`format JSON Schema keyword<https://json-schema.org/understanding-json-schema/reference/string#format>`_.The correspondence is given by replacing the ``_`` character in the name of thefunction with a ``-`` to obtain the format name and vice versa."""importbuiltinsimportloggingimportosimportreimportstringimporttypingfromitertoolsimportchainas_chainiftyping.TYPE_CHECKING:fromtyping_extensionsimportLiteral_logger=logging.getLogger(__name__)# -------------------------------------------------------------------------------------# PEP 440VERSION_PATTERN=r""" v? (?: (?:(?P<epoch>[0-9]+)!)? # epoch (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment (?P<pre> # pre-release [-_\.]? (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc) [-_\.]? (?P<pre_n>[0-9]+)? )? (?P<post> # post release (?:-(?P<post_n1>[0-9]+)) | (?: [-_\.]? (?P<post_l>post|rev|r) [-_\.]? (?P<post_n2>[0-9]+)? ) )? (?P<dev> # dev release [-_\.]? (?P<dev_l>dev) [-_\.]? (?P<dev_n>[0-9]+)? )? ) (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version"""VERSION_REGEX=re.compile(r"^\s*"+VERSION_PATTERN+r"\s*$",re.X|re.I)
[docs]defpep440(version:str)->bool:"""See :ref:`PyPA's version specification <pypa:version-specifiers>` (initially introduced in :pep:`440`). """returnVERSION_REGEX.match(version)isnotNone
# -------------------------------------------------------------------------------------# PEP 508PEP508_IDENTIFIER_PATTERN=r"([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])"PEP508_IDENTIFIER_REGEX=re.compile(f"^{PEP508_IDENTIFIER_PATTERN}$",re.I)
[docs]defpep508_identifier(name:str)->bool:"""See :ref:`PyPA's name specification <pypa:name-format>` (initially introduced in :pep:`508#names`). """returnPEP508_IDENTIFIER_REGEX.match(name)isnotNone
try:try:frompackagingimportrequirementsas_reqexceptImportError:# pragma: no cover# let's try setuptools vendored versionfromsetuptools._vendor.packagingimportrequirementsas_req# type: ignoredefpep508(value:str)->bool:"""See :ref:`PyPA's dependency specifiers <pypa:dependency-specifiers>` (initially introduced in :pep:`508`). """try:_req.Requirement(value)returnTrueexcept_req.InvalidRequirement:returnFalseexceptImportError:# pragma: no cover_logger.warning("Could not find an installation of `packaging`. Requirements, dependencies and ""versions might not be validated. ""To enforce validation, please install `packaging`.")
[docs]defpep508_versionspec(value:str)->bool:"""Expression that can be used to specify/lock versions (including ranges) See ``versionspec`` in :ref:`PyPA's dependency specifiers <pypa:dependency-specifiers>` (initially introduced in :pep:`508`). """ifany(cinvalueforcin(";","]","@")):# In PEP 508:# conditional markers, extras and URL specs are not included in the# versionspecreturnFalse# Let's pretend we have a dependency called `requirement` with the given# version spec, then we can reuse the pep508 function for validation:returnpep508(f"requirement{value}")
# -------------------------------------------------------------------------------------# PEP 517
[docs]defpep517_backend_reference(value:str)->bool:"""See PyPA's specification for defining build-backend references introduced in :pep:`517#source-trees`. This is similar to an entry-point reference (e.g., ``package.module:object``). """module,_,obj=value.partition(":")identifiers=(i.strip()foriin_chain(module.split("."),obj.split(".")))returnall(python_identifier(i)foriinidentifiersifi)
# -------------------------------------------------------------------------------------# Classifiers - PEP 301def_download_classifiers()->str:importsslfromemail.messageimportMessagefromurllib.requestimporturlopenurl="https://pypi.org/pypi?:action=list_classifiers"context=ssl.create_default_context()withurlopen(url,context=context)asresponse:# noqa: S310 (audit URLs)headers=Message()headers["content_type"]=response.getheader("content-type","text/plain")returnresponse.read().decode(headers.get_param("charset","utf-8"))# type: ignore[no-any-return]class_TroveClassifier:"""The ``trove_classifiers`` package is the official way of validating classifiers, however this package might not be always available. As a workaround we can still download a list from PyPI. We also don't want to be over strict about it, so simply skipping silently is an option (classifiers will be validated anyway during the upload to PyPI). """downloaded:typing.Union[None,"Literal[False]",typing.Set[str]]def__init__(self)->None:self.downloaded=Noneself._skip_download=False# None => not cached yet# False => cache not availableself.__name__="trove_classifier"# Emulate a public functiondef_disable_download(self)->None:# This is a private API. Only setuptools has the consent of using it.self._skip_download=Truedef__call__(self,value:str)->bool:ifself.downloadedisFalseorself._skip_downloadisTrue:returnTrueifos.getenv("NO_NETWORK")oros.getenv("VALIDATE_PYPROJECT_NO_NETWORK"):self.downloaded=Falsemsg=("Install ``trove-classifiers`` to ensure proper validation. ""Skipping download of classifiers list from PyPI (NO_NETWORK).")_logger.debug(msg)returnTrueifself.downloadedisNone:msg=("Install ``trove-classifiers`` to ensure proper validation. ""Meanwhile a list of classifiers will be downloaded from PyPI.")_logger.debug(msg)try:self.downloaded=set(_download_classifiers().splitlines())exceptException:self.downloaded=False_logger.debug("Problem with download, skipping validation")returnTruereturnvalueinself.downloadedorvalue.lower().startswith("private ::")try:fromtrove_classifiersimportclassifiersas_trove_classifiers
exceptImportError:# pragma: no covertrove_classifier=_TroveClassifier()# -------------------------------------------------------------------------------------# Stub packages - PEP 561
[docs]defpep561_stub_name(value:str)->bool:"""Name of a directory containing type stubs. It must follow the name scheme ``<package>-stubs`` as defined in :pep:`561#stub-only-packages`. """top,*children=value.split(".")ifnottop.endswith("-stubs"):returnFalsereturnpython_module_name(".".join([top[:-len("-stubs")],*children]))
# -------------------------------------------------------------------------------------# Non-PEP related
[docs]defurl(value:str)->bool:"""Valid URL (validation uses :obj:`urllib.parse`). For maximum compatibility please make sure to include a ``scheme`` prefix in your URL (e.g. ``http://``). """fromurllib.parseimporturlparsetry:parts=urlparse(value)ifnotparts.scheme:_logger.warning("For maximum compatibility please make sure to include a ""`scheme` prefix in your URL (e.g. 'http://'). "f"Given value: {value}")ifnot(value.startswith("/")orvalue.startswith("\\")or"@"invalue):parts=urlparse(f"http://{value}")returnbool(parts.schemeandparts.netloc)exceptException:returnFalse
[docs]defpython_identifier(value:str)->bool:"""Can be used as identifier in Python. (Validation uses :obj:`str.isidentifier`). """returnvalue.isidentifier()
[docs]defpython_qualified_identifier(value:str)->bool:""" Python "dotted identifier", i.e. a sequence of :obj:`python_identifier` concatenated with ``"."`` (e.g.: ``package.module.submodule``). """ifvalue.startswith(".")orvalue.endswith("."):returnFalsereturnall(python_identifier(m)forminvalue.split("."))
[docs]defpython_module_name(value:str)->bool:"""Module name that can be used in an ``import``-statement in Python. See :obj:`python_qualified_identifier`. """returnpython_qualified_identifier(value)
[docs]defpython_entrypoint_group(value:str)->bool:"""See ``Data model > group`` in the :ref:`PyPA's entry-points specification <pypa:entry-points>`. """returnENTRYPOINT_GROUP_REGEX.match(value)isnotNone
[docs]defpython_entrypoint_name(value:str)->bool:"""See ``Data model > name`` in the :ref:`PyPA's entry-points specification <pypa:entry-points>`. """ifnotENTRYPOINT_REGEX.match(value):returnFalseifnotRECOMMEDED_ENTRYPOINT_REGEX.match(value):msg=f"Entry point `{value}` does not follow recommended pattern: "msg+=RECOMMEDED_ENTRYPOINT_PATTERN_logger.warning(msg)returnTrue
[docs]defpython_entrypoint_reference(value:str)->bool:"""Reference to a Python object using in the format:: importable.module:object.attr See ``Data model >object reference`` in the :ref:`PyPA's entry-points specification <pypa:entry-points>`. """module,_,rest=value.partition(":")if"["inrest:obj,_,extras_=rest.partition("[")ifextras_.strip()[-1]!="]":returnFalseextras=(x.strip()forxinextras_.strip(string.whitespace+"[]").split(","))ifnotall(pep508_identifier(e)foreinextras):returnFalse_logger.warning(f"`{value}` - using extras for entry points is not recommended")else:obj=restmodule_parts=module.split(".")identifiers=_chain(module_parts,obj.split("."))ifrestelsemodule_partsreturnall(python_identifier(i.strip())foriinidentifiers)
[docs]defuint8(value:builtins.int)->bool:r"""Unsigned 8-bit integer (:math:`0 \leq x < 2^8`)"""return0<=value<2**8
[docs]defuint16(value:builtins.int)->bool:r"""Unsigned 16-bit integer (:math:`0 \leq x < 2^{16}`)"""return0<=value<2**16
[docs]defuint(value:builtins.int)->bool:r"""Unsigned 64-bit integer (:math:`0 \leq x < 2^{64}`)"""return0<=value<2**64
[docs]defint(value:builtins.int)->bool:r"""Signed 64-bit integer (:math:`-2^{63} \leq x < 2^{63}`)"""return-(2**63)<=value<2**63