#!/usr/bin/python3
"""Installer script for Pywikibot framework.
**How to create a new distribution:**
- replace the developmental version string in ``pywikibot.__metadata__.py``
by the corresponding final release
- create the package with::
make_dist -remote
- create a new tag with the version number of the final release
- synchronize the local tags with the remote repositoy
- merge current master branch to stable branch
- push new stable branch to Gerrit and merge it the stable repository
- prepare the next master release by increasing the version number in
``pywikibot.__metadata__.py`` and adding developmental identifier
- upload this patchset to Gerrit and merge it.
.. warning: do not upload a development release to pypi.
"""
#
# (C) Pywikibot team, 2009-2022
#
# Distributed under the terms of the MIT license.
#
import os
import re
import sys
from setuptools import setup
if sys.version_info[:3] >= (3, 9):
List = list
else:
from typing import List
# ------- setup extra_requires ------- #
extra_deps = {
# Core library dependencies
'eventstreams': ['sseclient<0.0.23,>=0.0.18'], # T222885
'isbn': ['python-stdnum>=1.17'],
'Graphviz': ['pydot>=1.2'],
'Google': ['google>=1.7'],
'memento': ['memento_client==0.6.1'],
'mwparserfromhell': ['mwparserfromhell>=0.5.0'],
'wikitextparser': ['wikitextparser>=0.47.0'],
'mysql': ['PyMySQL >= 0.9.3'], # toolforge
# vulnerability found in Pillow<8.1.1 but toolforge uses 5.4.1
'Tkinter': ['Pillow>=8.1.1'],
'mwoauth': ['mwoauth!=0.3.1,>=0.2.4'],
'html': ['BeautifulSoup4'],
'http': ['fake_useragent'],
'flake8': [ # Due to incompatibilities between packages the order matters.
'flake8>=5.0.2',
'darglint',
'pydocstyle>=4.0.0',
'flake8-bugbear!=21.4.1,!=21.11.28',
'flake8-coding',
'flake8-comprehensions>=3.1.4; python_version >= "3.8"',
'flake8-comprehensions>=2.2.0; python_version < "3.8"',
'flake8-docstrings>=1.3.1',
'verve-flake8-mock>=0.4',
'flake8-print>=2.0.1',
# flake8-quotes is incompatible with flake8 6 (T323752),
# jenkins CI ignores "3.8.1"
'flake8-quotes>=3.3.0; python_version < "3.8"',
'flake8-string-format',
'flake8-tuple>=0.2.8',
'flake8-no-u-prefixed-strings>=0.2',
'pep8-naming>=0.12.1, <0.13.0; python_version < "3.7"',
'pep8-naming>=0.12.1; python_version >= "3.7"',
],
'hacking': [
'hacking',
# importlib-metadata module already installed with hacking 4.1.0
# but importlib-metadata 5 fails, so adjust it
'importlib-metadata<5.0.0; python_version < "3.8"',
],
}
# ------- setup extra_requires for scripts ------- #
script_deps = {
'create_isbn_edition.py': ['isbnlib', 'unidecode'],
'commons_information.py': extra_deps['mwparserfromhell'],
'patrol.py': extra_deps['mwparserfromhell'],
'weblinkchecker.py': extra_deps['memento'],
}
extra_deps.update(script_deps)
extra_deps.update({'scripts': [i for k, v in script_deps.items() for i in v]})
# ------- setup install_requires ------- #
# packages which are mandatory
dependencies = [
'requests>=2.20.1, <2.28.0; python_version < "3.7"',
'requests>=2.20.1; python_version>="3.7"',
# PEP 440
'setuptools>=48.0.0 ; python_version >= "3.10"',
'setuptools>=38.5.2 ; python_version >= "3.7" and python_version < "3.10"',
'setuptools>=20.8.1, <59.7.0 ; python_version < "3.7"',
]
# in addition either mwparserfromhell or wikitextparser is required
# ------- setup tests_require ------- #
test_deps = ['mock']
# Add all dependencies as test dependencies,
# so all scripts can be compiled for script_tests, etc.
if 'PYSETUP_TEST_EXTRAS' in os.environ: # pragma: no cover
test_deps += [i for k, v in extra_deps.items() if k != 'flake8' for i in v]
# These extra dependencies are needed other unittest fails to load tests.
test_deps += extra_deps['eventstreams']
class _DottedDict(dict):
__getattr__ = dict.__getitem__
# import metadata
metadata = _DottedDict()
name = 'pywikibot'
path = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(path, name, '__metadata__.py')) as f:
exec(f.read(), None, metadata)
assert metadata.__name__ == name
[docs]def get_validated_version() -> str: # pragma: no cover
"""Get a validated pywikibot module version string.
The version number from pywikibot.__metadata__.__version__ is used.
setup.py with 'sdist' option is used to create a new source distribution.
In that case the version number is validated: Read tags from git.
Verify that the new release is higher than the last repository tag
and is not a developmental release.
:return: pywikibot module version string
:rtype: str
"""
version = metadata.__version__
if 'sdist' not in sys.argv:
return version
# validate version for sdist
from contextlib import suppress
from subprocess import PIPE, run
from pkg_resources import parse_version, safe_version
try:
tags = run(['git', 'tag'], check=True, stdout=PIPE,
universal_newlines=True).stdout.splitlines()
except Exception as e:
print(e)
sys.exit('Creating source distribution canceled.')
for tag in ('stable', 'python2'):
with suppress(ValueError):
tags.remove(tag)
last_tag = tags[-1]
warnings = []
if parse_version(version) < parse_version('0'):
# any version which is not a valid PEP 440 version will be considered
# less than any valid PEP 440 version
warnings.append(
version + ' is not a valid version string following PEP 440.')
elif safe_version(version) != version:
warnings.append(
'{} does not follow PEP 440. Use {} as version string instead.'
.format(version, safe_version(version)))
if parse_version(version) <= parse_version(last_tag):
warnings.append(
'New version "{}" is not higher than last version "{}".'
.format(version, last_tag))
if warnings:
print(__doc__)
print('\n\n'.join(warnings))
sys.exit('\nBuild of distribution package canceled.')
return version
[docs]def read_desc(filename) -> str: # pragma: no cover
"""Read long description.
Combine included restructured text files which must be done before
uploading because the source isn't available after creating the package.
"""
pattern = r'\:\w+\:`([^`]+?)(?:<.+>)?`', r'\1'
desc = []
with open(filename) as f:
for line in f:
if line.strip().startswith('.. include::'):
include = os.path.relpath(line.rsplit('::')[1].strip())
if os.path.exists(include):
with open(include) as g:
desc.append(re.sub(pattern[0], pattern[1], g.read()))
else:
print('Cannot include {}; file not found'.format(include))
else:
desc.append(re.sub(pattern[0], pattern[1], line))
return ''.join(desc)
[docs]def get_packages(name) -> List[str]: # pragma: no cover
"""Find framework packages."""
try:
from setuptools import find_namespace_packages
except ImportError:
sys.exit(
'setuptools >= 40.1.0 is required to create a new distribution.')
packages = find_namespace_packages(include=[name + '.*'])
return [str(name)] + packages
[docs]def main() -> None: # pragma: no cover
"""Setup entry point."""
version = get_validated_version()
setup(
name=metadata.__name__,
version=version,
description=metadata.__description__,
long_description=read_desc('README.rst'),
# long_description_content_type
# author
# author_email
maintainer=metadata.__maintainer__,
maintainer_email=metadata.__maintainer_email__,
url=metadata.__url__,
download_url=metadata.__download_url__,
packages=get_packages(name),
# py_modules
# scripts
# ext_package
# ext_modules
# distclass
# script_name
# script_args
# options
license=metadata.__license__,
# license_files
keywords=metadata.__keywords__.split(),
# platforms
# cmdclass
# package_dir
include_package_data=True,
# exclude_package_data
# package_data
# zip_safe
install_requires=dependencies,
extras_require=extra_deps,
python_requires='>=3.6.1',
# namespace_packages
test_suite='tests.collector',
tests_require=test_deps,
# test_loader
# eager_resources
project_urls={
'Documentation': 'https://doc.wikimedia.org/pywikibot/stable/',
'Source':
'https://gerrit.wikimedia.org/r/plugins/gitiles/pywikibot/core/', # noqa: E501
'GitHub Mirror': 'https://github.com/wikimedia/pywikibot',
'Tracker': 'https://phabricator.wikimedia.org/tag/pywikibot/',
},
entry_points={
'console_scripts': [
'pwb = pywikibot.scripts.wrapper:run',
],
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: Afrikaans',
'Natural Language :: Arabic',
'Natural Language :: Basque',
'Natural Language :: Bengali',
'Natural Language :: Bosnian',
'Natural Language :: Bulgarian',
'Natural Language :: Cantonese',
'Natural Language :: Catalan',
'Natural Language :: Chinese (Simplified)',
'Natural Language :: Chinese (Traditional)',
'Natural Language :: Croatian',
'Natural Language :: Czech',
'Natural Language :: Danish',
'Natural Language :: Dutch',
'Natural Language :: English',
'Natural Language :: Esperanto',
'Natural Language :: Finnish',
'Natural Language :: French',
'Natural Language :: Galician',
'Natural Language :: German',
'Natural Language :: Greek',
'Natural Language :: Hebrew',
'Natural Language :: Hindi',
'Natural Language :: Hungarian',
'Natural Language :: Icelandic',
'Natural Language :: Indonesian',
'Natural Language :: Irish',
'Natural Language :: Italian',
'Natural Language :: Japanese',
'Natural Language :: Javanese',
'Natural Language :: Korean',
'Natural Language :: Latin',
'Natural Language :: Latvian',
'Natural Language :: Lithuanian',
'Natural Language :: Macedonian',
'Natural Language :: Malay',
'Natural Language :: Marathi',
'Natural Language :: Nepali',
'Natural Language :: Norwegian',
'Natural Language :: Panjabi',
'Natural Language :: Persian',
'Natural Language :: Polish',
'Natural Language :: Portuguese',
'Natural Language :: Portuguese (Brazilian)',
'Natural Language :: Romanian',
'Natural Language :: Russian',
'Natural Language :: Serbian',
'Natural Language :: Slovak',
'Natural Language :: Slovenian',
'Natural Language :: Spanish',
'Natural Language :: Swedish',
'Natural Language :: Tamil',
'Natural Language :: Telugu',
'Natural Language :: Thai',
'Natural Language :: Tibetan',
'Natural Language :: Turkish',
'Natural Language :: Ukrainian',
'Natural Language :: Urdu',
'Natural Language :: Vietnamese',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Wiki',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities',
],
)
# Finally show distribution version before uploading
if 'sdist' in sys.argv:
print('\nDistribution package created for version {}'.format(version))
if __name__ == '__main__': # pragma: no cover
main()