diff --git a/newsfragments/2971.removal.rst b/newsfragments/2971.removal.rst new file mode 100644 index 0000000000..940453ab0a --- /dev/null +++ b/newsfragments/2971.removal.rst @@ -0,0 +1 @@ +Removed upload_docs command. \ No newline at end of file diff --git a/setuptools/command/register.py b/setuptools/command/register.py deleted file mode 100644 index 93ef04aa0e..0000000000 --- a/setuptools/command/register.py +++ /dev/null @@ -1,22 +0,0 @@ -from setuptools.errors import RemovedCommandError - -from ..dist import Distribution - -import distutils.command.register as orig -from distutils import log - - -class register(orig.register): - """Formerly used to register packages on PyPI.""" - - distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution - - def run(self): - msg = ( - "The register command has been removed, use twine to upload " - "instead (https://pypi.org/p/twine)" - ) - - self.announce("ERROR: " + msg, log.ERROR) - - raise RemovedCommandError(msg) diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py deleted file mode 100644 index 649b41fa30..0000000000 --- a/setuptools/command/upload.py +++ /dev/null @@ -1,20 +0,0 @@ -from setuptools.dist import Distribution -from setuptools.errors import RemovedCommandError - -from distutils import log -from distutils.command import upload as orig - - -class upload(orig.upload): - """Formerly used to upload packages to PyPI.""" - - distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution - - def run(self): - msg = ( - "The upload command has been removed, use twine to upload " - "instead (https://pypi.org/p/twine)" - ) - - self.announce("ERROR: " + msg, log.ERROR) - raise RemovedCommandError(msg) diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py deleted file mode 100644 index 3c2946cfc8..0000000000 --- a/setuptools/command/upload_docs.py +++ /dev/null @@ -1,221 +0,0 @@ -"""upload_docs - -Implements a Distutils 'upload_docs' subcommand (upload documentation to -sites other than PyPi such as devpi). -""" - -import functools -import http.client -import itertools -import os -import shutil -import tempfile -import urllib.parse -import zipfile -from base64 import standard_b64encode - -from .._importlib import metadata -from ..warnings import SetuptoolsDeprecationWarning -from .upload import upload - -from distutils import log -from distutils.errors import DistutilsOptionError - - -def _encode(s): - return s.encode('utf-8', 'surrogateescape') - - -class upload_docs(upload): - # override the default repository as upload_docs isn't - # supported by Warehouse (and won't be). - DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/' - - description = 'Upload documentation to sites other than PyPi such as devpi' - - user_options = [ - ( - 'repository=', - 'r', - "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY, - ), - ('show-response', None, 'display full response text from server'), - ('upload-dir=', None, 'directory to upload'), - ] - boolean_options = upload.boolean_options - - def has_sphinx(self): - return bool( - self.upload_dir is None - and metadata.entry_points(group='distutils.commands', name='build_sphinx') - ) - - sub_commands = [('build_sphinx', has_sphinx)] - - def initialize_options(self): - upload.initialize_options(self) - self.upload_dir = None - self.target_dir = None - - def finalize_options(self): - log.warn( - "Upload_docs command is deprecated. Use Read the Docs " - "(https://readthedocs.org) instead." - ) - upload.finalize_options(self) - if self.upload_dir is None: - if self.has_sphinx(): - build_sphinx = self.get_finalized_command('build_sphinx') - self.target_dir = dict(build_sphinx.builder_target_dirs)['html'] - else: - build = self.get_finalized_command('build') - self.target_dir = os.path.join(build.build_base, 'docs') - else: - self.ensure_dirname('upload_dir') - self.target_dir = self.upload_dir - self.announce('Using upload directory %s' % self.target_dir) - - def create_zipfile(self, filename): - zip_file = zipfile.ZipFile(filename, "w") - try: - self.mkpath(self.target_dir) # just in case - for root, dirs, files in os.walk(self.target_dir): - if root == self.target_dir and not files: - tmpl = "no files found in upload directory '%s'" - raise DistutilsOptionError(tmpl % self.target_dir) - for name in files: - full = os.path.join(root, name) - relative = root[len(self.target_dir) :].lstrip(os.path.sep) - dest = os.path.join(relative, name) - zip_file.write(full, dest) - finally: - zip_file.close() - - def run(self): - SetuptoolsDeprecationWarning.emit( - "Deprecated command", - """ - upload_docs is deprecated and will be removed in a future version. - Instead, use tools like devpi and Read the Docs; or lower level tools like - httpie and curl to interact directly with your hosting service API. - """, - due_date=(2023, 9, 26), # warning introduced in 27 Jul 2022 - ) - - # Run sub commands - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - tmp_dir = tempfile.mkdtemp() - name = self.distribution.metadata.get_name() - zip_file = os.path.join(tmp_dir, "%s.zip" % name) - try: - self.create_zipfile(zip_file) - self.upload_file(zip_file) - finally: - shutil.rmtree(tmp_dir) - - @staticmethod - def _build_part(item, sep_boundary): - key, values = item - title = '\nContent-Disposition: form-data; name="%s"' % key - # handle multiple entries for the same name - if not isinstance(values, list): - values = [values] - for value in values: - if isinstance(value, tuple): - title += '; filename="%s"' % value[0] - value = value[1] - else: - value = _encode(value) - yield sep_boundary - yield _encode(title) - yield b"\n\n" - yield value - if value and value[-1:] == b'\r': - yield b'\n' # write an extra newline (lurve Macs) - - @classmethod - def _build_multipart(cls, data): - """ - Build up the MIME payload for the POST data - """ - boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\n--' + boundary.encode('ascii') - end_boundary = sep_boundary + b'--' - end_items = ( - end_boundary, - b"\n", - ) - builder = functools.partial( - cls._build_part, - sep_boundary=sep_boundary, - ) - part_groups = map(builder, data.items()) - parts = itertools.chain.from_iterable(part_groups) - body_items = itertools.chain(parts, end_items) - content_type = 'multipart/form-data; boundary=%s' % boundary - return b''.join(body_items), content_type - - def upload_file(self, filename): - with open(filename, 'rb') as f: - content = f.read() - meta = self.distribution.metadata - data = { - ':action': 'doc_upload', - 'name': meta.get_name(), - 'content': (os.path.basename(filename), content), - } - # set up the authentication - credentials = _encode(self.username + ':' + self.password) - credentials = standard_b64encode(credentials).decode('ascii') - auth = "Basic " + credentials - - body, ct = self._build_multipart(data) - - msg = "Submitting documentation to %s" % (self.repository) - self.announce(msg, log.INFO) - - # build the Request - # We can't use urllib2 since we need to send the Basic - # auth right with the first request - schema, netloc, url, params, query, fragments = urllib.parse.urlparse( - self.repository - ) - assert not params and not query and not fragments - if schema == 'http': - conn = http.client.HTTPConnection(netloc) - elif schema == 'https': - conn = http.client.HTTPSConnection(netloc) - else: - raise AssertionError("unsupported schema " + schema) - - data = '' - try: - conn.connect() - conn.putrequest("POST", url) - content_type = ct - conn.putheader('Content-type', content_type) - conn.putheader('Content-length', str(len(body))) - conn.putheader('Authorization', auth) - conn.endheaders() - conn.send(body) - except OSError as e: - self.announce(str(e), log.ERROR) - return - - r = conn.getresponse() - if r.status == 200: - msg = 'Server response (%s): %s' % (r.status, r.reason) - self.announce(msg, log.INFO) - elif r.status == 301: - location = r.getheader('Location') - if location is None: - location = 'https://pythonhosted.org/%s/' % meta.get_name() - msg = 'Upload successful. Visit %s' % location - self.announce(msg, log.INFO) - else: - msg = 'Upload failed (%s): %s' % (r.status, r.reason) - self.announce(msg, log.ERROR) - if self.show_response: - print('-' * 75, r.read(), '-' * 75) diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py deleted file mode 100644 index 0c7d109d31..0000000000 --- a/setuptools/tests/test_register.py +++ /dev/null @@ -1,19 +0,0 @@ -from unittest import mock - -import pytest - -from setuptools.command.register import register -from setuptools.dist import Distribution -from setuptools.errors import RemovedCommandError - - -class TestRegister: - def test_register_exception(self): - """Ensure that the register command has been properly removed.""" - dist = Distribution() - dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] - - cmd = register(dist) - - with pytest.raises(RemovedCommandError): - cmd.run() diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py deleted file mode 100644 index cbcd455c41..0000000000 --- a/setuptools/tests/test_upload.py +++ /dev/null @@ -1,19 +0,0 @@ -from unittest import mock - -import pytest - -from setuptools.command.upload import upload -from setuptools.dist import Distribution -from setuptools.errors import RemovedCommandError - - -class TestUpload: - def test_upload_exception(self): - """Ensure that the register command has been properly removed.""" - dist = Distribution() - dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] - - cmd = upload(dist) - - with pytest.raises(RemovedCommandError): - cmd.run()