#!/usr/bin/env python # -*- coding: utf-8 -*- """ ISO Standards Setup. Standard setup.py file which is used to package and distribute a Python package with Distutils so that it can be published to a PyPI server. This file belongs in the root directory of a project repository. This is an example that uses some basic options which are available for the setup of a PyPI package. It also reads and gathers example dependencies for a package. See: https://pythonhosted.org/setuptools/setuptools.html for more details. """ import configparser import hashlib import os from setuptools import Command from setuptools import find_packages from setuptools import setup from content_utils import __version__ AVRO_REQUIRES = ['kafka-utils[avro]~=4.1'] class register(Command): """No-op: setup.py register was removed from setuptools.""" description = 'no-op register (removed in modern setuptools)' user_options = [('repository=', 'r', 'repository name / URL (ignored)')] def initialize_options(self): """No-op.""" self.repository = None def finalize_options(self): """No-op.""" def run(self): """No-op.""" class upload(Command): """Upload dist files to a PyPI server (replaces removed setuptools upload).""" description = 'upload dist files to a PyPI-compatible server' user_options = [('repository=', 'r', 'repository name from .pypirc')] def initialize_options(self): """Set default repository.""" self.repository = 'pypi' def finalize_options(self): """No-op.""" def run(self): """Read ~/.pypirc and POST each dist file to the repository.""" # Imported lazily so setup.py remains importable before requests is installed. import requests # Read repository URL and credentials from ~/.pypirc using the -r value. rc_path = os.path.expanduser('~/.pypirc') cfg = configparser.RawConfigParser() if not cfg.read(rc_path): raise SystemExit(f'upload: {rc_path} not found') try: repo_url = cfg.get(self.repository, 'repository') username = cfg.get(self.repository, 'username') password = cfg.get(self.repository, 'password') except configparser.NoSectionError: raise SystemExit( f'upload: section [{self.repository}] not found in {rc_path}' ) # dist_files is populated by the preceding sdist/bdist_wheel commands in # the same invocation (e.g. `setup.py sdist bdist_wheel upload`). dist_files = getattr(self.distribution, 'dist_files', []) if not dist_files: raise SystemExit( 'upload: no dist files found; run sdist and/or bdist_wheel first' ) for _cmd, _pyver, filename in dist_files: # Compute MD5 in chunks to avoid loading large artifacts into memory. digest = hashlib.md5() # noqa: S324 with open(filename, 'rb') as fh: for chunk in iter(lambda: fh.read(65536), b''): digest.update(chunk) # Stream the file directly to requests rather than buffering it. with open(filename, 'rb') as fh: resp = requests.post( repo_url, auth=(username, password), data={ ':action': 'file_upload', 'name': self.distribution.get_name(), 'version': self.distribution.get_version(), 'md5_digest': digest.hexdigest(), }, files={ 'content': ( os.path.basename(filename), fh, 'application/octet-stream', ) }, timeout=120, ) resp.raise_for_status() print(f'Uploaded {os.path.basename(filename)} to {repo_url}') def read_requirements(path): """Read requirements from file based on file path provided. Lines beginning with a dash are excluded, e.g. references to an internal PyPI server url (-i). Args: path (str): File path for requirements text file. Returns: Array of requirements libraries. """ with open(path, 'r') as fd: requirements = [ req.strip() for req in fd.readlines() if not req.startswith('-')] return requirements install_requires = list(read_requirements('requirements.txt')) setup( cmdclass={'register': register, 'upload': upload}, name='content_utils', version=__version__, description='Shareable utils for content review.', author='The Orchard', author_email='webdev@theorchard.com', url='https://github.com/theorchard/python-content-utils', packages=[*find_packages(), 'content_utils.constants.gql'], include_package_data=True, data_files=[ ('gql', [ 'content_utils/constants/gql/add_product_mutation.gql', 'content_utils/constants/gql/base_product_query.gql', 'content_utils/constants/gql/complete_product_query.gql', 'content_utils/constants/gql/indexable_product_query.gql', 'content_utils/constants/gql/meta_language_query.gql', 'content_utils/constants/gql/review_queue_item_query.gql', 'content_utils/constants/gql/genre_query.gql' ]) ], test_suite='tests', zip_safe=False, python_requires='>=3.13', install_requires=install_requires, extras_require={ 'avro': AVRO_REQUIRES } )