"""Internal library for manipulating package dependencies and requirements."""importrefromitertoolsimportchainfromtypingimportIterable,Listfrompackaging.requirementsimportInvalidRequirement,Requirement# setuptools version is now enforced via `install_requires`BUILD=("setuptools_scm>=5",)"""Dependencies that will be required to build the created project"""RUNTIME=('importlib-metadata; python_version<"3.8"',)# TODO: Remove `importlib-metadata` when `python_requires = >= 3.8`"""Dependencies that will be required at runtime by the created project"""ISOLATED=("setuptools>=46.1.0","setuptools_scm[toml]>=5",*BUILD[1:])"""Dependencies for isolated builds (:pep:`517`/:pep:`518`).- setuptools min version might be slightly higher then the version required at runtime.- setuptools_scm requires an optional dependency to work with pyproject.toml"""# Although version 36.6.0 introduces PEP517 implementation,# version 46.1.0 fix a bug with setuptools.finalize_distribution_options,# which is a hook used by setuptools_scm (better safe then sorry).REQ_SPLITTER=re.compile(r";(?!\s*(python|platform|implementation|os|sys)_)",re.M)"""Regex to split requirements that considers both `setup.cfg specs`_ and :pep:`508`(in a *good enough* simplified fashion)... _setup.cfg specs: https://setuptools.pypa.io/en/latest/userguide/declarative_config.html"""# noqa
[docs]defsplit(requirements:str)->List[str]:"""Split a combined requirement string (such as the values for ``setup_requires`` and ``install_requires`` in ``setup.cfg``) into a list of individual requirement strings, that can be used in :obj:`is_included`, :obj:`get_requirements_str`, :obj:`remove`, etc... """lines=requirements.splitlines()deps=(dep.strip()forlineinlinesfordepinREQ_SPLITTER.split(line)ifdep)return[depfordepindepsifdep]# Remove empty deps
[docs]defdeduplicate(requirements:Iterable[str])->List[str]:"""Given a sequence of individual requirement strings, e.g. ``["platformdirs>=1.4.4", "packaging>20.0"]``, remove the duplicated packages. If a package is duplicated, the last occurrence stays. """returnlist({attempt_pkg_name(r):rforrinrequirements}.values())
[docs]defremove(requirements:Iterable[str],to_remove:Iterable[str])->List[str]:"""Given a list of individual requirement strings, e.g. ``["platformdirs>=1.4.4", "packaging>20.0"]``, remove the requirements in ``to_remove``. """removable={attempt_pkg_name(r)forrinto_remove}return[rforrinrequirementsifattempt_pkg_name(r)notinremovable]
[docs]defadd(requirements:Iterable[str],to_add:Iterable[str]=BUILD)->List[str]:"""Given a sequence of individual requirement strings, add ``to_add`` to it. By default adds :obj:`BUILD` if ``to_add`` is not given."""returndeduplicate(chain(requirements,to_add))
[docs]defattempt_pkg_name(requirement:str)->str:"""In the case the given string is a dependency specification (:pep:`508`/ :pep`440`), it returns the "package name" part of dependency (without versions). Otherwise, it returns the same string (removed the comment marks). """req=requirement.strip("#").strip()try:returnRequirement(req).nameexceptInvalidRequirement:returnreq