#!/usr/bin/env python3 # -*- coding: utf-8 -*- """The setup script.""" from collections import namedtuple from html.parser import HTMLParser import os import pkg_resources from setuptools import setup, find_packages # noqa from setuptools.command.develop import develop # noqa from setuptools.command.install import install # noqa from setuptools.command.bdist_egg import bdist_egg # noqa import sys import tarfile import typing as T from urllib.error import HTTPError, URLError from urllib.request import urlopen from wheel.bdist_wheel import bdist_wheel # noqa import zipfile GECKO_RELEASE_PATH = "https://github.com/mozilla/geckodriver" PKG_NAME = 'bot_z' VERSION = '0.1.0' AUTHOR = 'blallo' AUTHOR_EMAIL = 'blallo@autistici.org' BIN_PATH = 'bin/geckodriver' with open('README.md') as readme_file: readme = readme_file.read() requirements = [ 'Click>=6.0', 'selenium>=3.141.0', ] setup_requirements = [ ] # type: T.List[str] test_requirements = [ ] # type: T.List[str] class GitTags(HTMLParser): tags: T.List[str] = list() take_next = 0 def handle_starttag(self, tag, attrs): dattrs = dict(attrs) if 'commit-title' in dattrs.get('class', ''): self.take_next = 1 def handle_data(self, data): if self.take_next == 0: return elif self.take_next == 1: self.take_next = 2 elif self.take_next == 2: self.tags.append(data.strip('\n').strip(' ').strip('\n')) self.take_next = 0 def retrieve_page(url: str) -> bytes: """ Auxiliary function to download html body from and URI, handling the errors. """ try: with urlopen(url) as conn: content = conn.read() except HTTPError as e: print("Connection error: {!s}".format(e)) raise except URLError as e: print("Check the URI: {!s}".format(e)) raise return content def find_latest_version(url: str) -> str: """ Retrieves latest geckodriver tag. """ tag_page = retrieve_page('{}/tags'.format(url)) gt = GitTags() gt.feed(tag_page.decode('utf-8')) gt.tags.sort() return gt.tags[-1] def verify_if_superuser() -> bool: """ Checks if uid or euid is 0. """ _uid = os.getuid() _euid = os.geteuid() return _uid == 0 or _euid == 0 def ensure_local_folder() -> None: """ Create a bin/ folder in the current package installation path. """ bin_path = pkg_resources.resource_filename(PKG_NAME, BIN_PATH) print("[LOCAL_FOLDER] ensuring local folder: {}".format(bin_path)) pkg_resources.ensure_directory(bin_path) def assemble_driver_uri( version: T.Optional[str]=None, platform: T.Optional[str]=None ) -> str: """ Selects the right geckodriver URI. """ # TODO: use pkg_resources.get_platform() if not version: version = find_latest_version(GECKO_RELEASE_PATH) if not platform: s_platform = sys.platform is_64bits = sys.maxsize > 2**32 if is_64bits: platform = '{}64'.format(s_platform) else: platform = '{}64'.format(s_platform) if 'win' in platform: ext = 'zip' else: ext = 'tar.gz' return '{base}/releases/download/{vers}/geckodriver-{vers}-{platform}.{ext}'.format( base=GECKO_RELEASE_PATH, vers=version, platform=platform, ext=ext ) def download_driver_bin(uri: str, path: str) -> None: """ Donwloads the geckodriver binary. """ name = uri.split('/')[-1] filepath = os.path.join(path, name) print("[DRIVER] downloading '{}' to {}".format(uri, filepath)) content = retrieve_page(uri) try: with open(filepath, 'wb') as f: f.write(content) if name.endswith(".zip"): with zipfile.ZipFile(filepath, 'r') as z: z.extractall(path) elif name.endswith(".tar.gz"): with tarfile.open(filepath, 'r') as r: r.extractall(path) else: raise RuntimeError("Unrecognized file extension: %s", name) finally: os.remove(filepath) def preinstall(platform: T.Optional[str]=None) -> None: """ Performs all the postintallation flow, donwloading in the right place the geckodriver binary. """ # target_path = os.path.join(os.path.abspath(os.path.curdir), 'bot_z', 'bin') target_path = pkg_resources.resource_filename('bot_z', 'bin') ensure_local_folder() version = os.environ.get('BOTZ_GECKO_VERSION') gecko_uri = assemble_driver_uri(version, platform) print("[POSTINSTALL] gecko_uri: {}".format(gecko_uri)) download_driver_bin(gecko_uri, target_path) def translate_platform_to_gecko_vers(plat: str) -> str: """ Map appropriately the platform provided on the command line to the one used by PEP 513. """ if plat is None: return None PLATS = { "win32": "win32", "win-amd64": "win64", "manylinux1-i686": "linux32", "manylinux1-x86_64": "linux64", "macosx": "macos", } try: return PLATS[plat] except KeyError as e: print("Allowed platforms are: {!r}".format(list(PLATS.keys()))) raise # From: https://stackoverflow.com/a/36902139 class CustomDevelopCommand(develop): """Custom installation for development mode.""" def run(self): print("POSTINSTALL") preinstall() super().run() class CustomInstallCommand(install): """Custom installation for installation mode.""" def run(self): opts = self.distribution.get_cmdline_options() platform = None if 'bdist_wheel' in opts: platform = translate_platform_to_gecko_vers( opts['bdist_wheel'].get('plat-name') ) preinstall(platform) super().run() # From: https://stackoverflow.com/a/45150383 class CustomBDistWheel(bdist_wheel): """ Custom bdist_wheel command to ship the right binary of geckodriver. """ def finalize_options(self): super().finalize_options() self.root_is_pure = False def get_tag(self): python, abi, plat = super().get_tag() python, abi = 'py3', 'none' return python, abi, plat setup( name=PKG_NAME, version=VERSION, description="A bot to easen the daily routine with zucchetti virtual badge.", long_description=readme, author=AUTHOR, author_email=AUTHOR_EMAIL, url='https://git.abbiamoundominio.org/blallo/BotZ', packages=find_packages(include=['bot_z']), cmdclass={ 'develop': CustomDevelopCommand, 'install': CustomInstallCommand, 'bdist_wheel': CustomBDistWheel, }, entry_points={ 'console_scripts': [ 'bot_z=bot_z.cli:main' ] }, package_data = {'bot_z': ['bin/geckodriver']}, include_package_data=True, install_requires=requirements, license="GLWTS Public Licence", zip_safe=False, keywords='bot_z', classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', 'License :: GLWTS Public Licence', 'Natural Language :: English', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', ], test_suite='pytest', tests_require=test_requirements, setup_requires=setup_requirements, )