mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	initial import of the packaging package in the standard library
This commit is contained in:
		
							parent
							
								
									566f8a646e
								
							
						
					
					
						commit
						1231a4e097
					
				
					 193 changed files with 30376 additions and 149 deletions
				
			
		
							
								
								
									
										173
									
								
								Lib/packaging/command/upload_docs.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								Lib/packaging/command/upload_docs.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,173 @@ | |||
| """Upload HTML documentation to a project index.""" | ||||
| 
 | ||||
| import os | ||||
| import base64 | ||||
| import socket | ||||
| import zipfile | ||||
| import logging | ||||
| import http.client | ||||
| import urllib.parse | ||||
| from io import BytesIO | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.util import read_pypirc, DEFAULT_REPOSITORY, DEFAULT_REALM | ||||
| from packaging.errors import PackagingFileError | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| 
 | ||||
| def zip_dir(directory): | ||||
|     """Compresses recursively contents of directory into a BytesIO object""" | ||||
|     destination = BytesIO() | ||||
|     zip_file = zipfile.ZipFile(destination, "w") | ||||
|     for root, dirs, files in os.walk(directory): | ||||
|         for name in files: | ||||
|             full = os.path.join(root, name) | ||||
|             relative = root[len(directory):].lstrip(os.path.sep) | ||||
|             dest = os.path.join(relative, name) | ||||
|             zip_file.write(full, dest) | ||||
|     zip_file.close() | ||||
|     return destination | ||||
| 
 | ||||
| 
 | ||||
| # grabbed from | ||||
| #    http://code.activestate.com/recipes/ | ||||
| #    146306-http-client-to-post-using-multipartform-data/ | ||||
| # TODO factor this out for use by install and command/upload | ||||
| 
 | ||||
| def encode_multipart(fields, files, boundary=None): | ||||
|     """ | ||||
|     *fields* is a sequence of (name: str, value: str) elements for regular | ||||
|     form fields, *files* is a sequence of (name: str, filename: str, value: | ||||
|     bytes) elements for data to be uploaded as files. | ||||
| 
 | ||||
|     Returns (content_type: bytes, body: bytes) ready for http.client.HTTP. | ||||
|     """ | ||||
|     if boundary is None: | ||||
|         boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' | ||||
|     elif not isinstance(boundary, bytes): | ||||
|         raise TypeError('boundary is not bytes but %r' % type(boundary)) | ||||
| 
 | ||||
|     l = [] | ||||
|     for key, value in fields: | ||||
|         l.extend(( | ||||
|             b'--' + boundary, | ||||
|             ('Content-Disposition: form-data; name="%s"' % | ||||
|              key).encode('utf-8'), | ||||
|             b'', | ||||
|             value.encode('utf-8'))) | ||||
| 
 | ||||
|     for key, filename, value in files: | ||||
|         l.extend(( | ||||
|             b'--' + boundary, | ||||
|             ('Content-Disposition: form-data; name="%s"; filename="%s"' % | ||||
|              (key, filename)).encode('utf-8'), | ||||
|             b'', | ||||
|             value)) | ||||
|     l.append(b'--' + boundary + b'--') | ||||
|     l.append(b'') | ||||
| 
 | ||||
|     body = b'\r\n'.join(l) | ||||
| 
 | ||||
|     content_type = b'multipart/form-data; boundary=' + boundary | ||||
|     return content_type, body | ||||
| 
 | ||||
| 
 | ||||
| class upload_docs(Command): | ||||
| 
 | ||||
|     description = "upload HTML documentation to PyPI" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('repository=', 'r', | ||||
|          "repository URL [default: %s]" % DEFAULT_REPOSITORY), | ||||
|         ('show-response', None, | ||||
|          "display full response text from server"), | ||||
|         ('upload-dir=', None, | ||||
|          "directory to upload"), | ||||
|         ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.repository = None | ||||
|         self.realm = None | ||||
|         self.show_response = False | ||||
|         self.upload_dir = None | ||||
|         self.username = '' | ||||
|         self.password = '' | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.repository is None: | ||||
|             self.repository = DEFAULT_REPOSITORY | ||||
|         if self.realm is None: | ||||
|             self.realm = DEFAULT_REALM | ||||
|         if self.upload_dir is None: | ||||
|             build = self.get_finalized_command('build') | ||||
|             self.upload_dir = os.path.join(build.build_base, "docs") | ||||
|             if not os.path.isdir(self.upload_dir): | ||||
|                 self.upload_dir = os.path.join(build.build_base, "doc") | ||||
|         logger.info('Using upload directory %s', self.upload_dir) | ||||
|         self.verify_upload_dir(self.upload_dir) | ||||
|         config = read_pypirc(self.repository, self.realm) | ||||
|         if config != {}: | ||||
|             self.username = config['username'] | ||||
|             self.password = config['password'] | ||||
|             self.repository = config['repository'] | ||||
|             self.realm = config['realm'] | ||||
| 
 | ||||
|     def verify_upload_dir(self, upload_dir): | ||||
|         self.ensure_dirname('upload_dir') | ||||
|         index_location = os.path.join(upload_dir, "index.html") | ||||
|         if not os.path.exists(index_location): | ||||
|             mesg = "No 'index.html found in docs directory (%s)" | ||||
|             raise PackagingFileError(mesg % upload_dir) | ||||
| 
 | ||||
|     def run(self): | ||||
|         name = self.distribution.metadata['Name'] | ||||
|         version = self.distribution.metadata['Version'] | ||||
|         zip_file = zip_dir(self.upload_dir) | ||||
| 
 | ||||
|         fields = [(':action', 'doc_upload'), | ||||
|                   ('name', name), ('version', version)] | ||||
|         files = [('content', name, zip_file.getvalue())] | ||||
|         content_type, body = encode_multipart(fields, files) | ||||
| 
 | ||||
|         credentials = self.username + ':' + self.password | ||||
|         auth = b"Basic " + base64.encodebytes(credentials.encode()).strip() | ||||
| 
 | ||||
|         logger.info("Submitting documentation to %s", self.repository) | ||||
| 
 | ||||
|         scheme, netloc, url, params, query, fragments = urllib.parse.urlparse( | ||||
|             self.repository) | ||||
|         if scheme == "http": | ||||
|             conn = http.client.HTTPConnection(netloc) | ||||
|         elif scheme == "https": | ||||
|             conn = http.client.HTTPSConnection(netloc) | ||||
|         else: | ||||
|             raise AssertionError("unsupported scheme %r" % scheme) | ||||
| 
 | ||||
|         try: | ||||
|             conn.connect() | ||||
|             conn.putrequest("POST", url) | ||||
|             conn.putheader('Content-type', content_type) | ||||
|             conn.putheader('Content-length', str(len(body))) | ||||
|             conn.putheader('Authorization', auth) | ||||
|             conn.endheaders() | ||||
|             conn.send(body) | ||||
| 
 | ||||
|         except socket.error as e: | ||||
|             logger.error(e) | ||||
|             return | ||||
| 
 | ||||
|         r = conn.getresponse() | ||||
| 
 | ||||
|         if r.status == 200: | ||||
|             logger.info('Server response (%s): %s', r.status, r.reason) | ||||
|         elif r.status == 301: | ||||
|             location = r.getheader('Location') | ||||
|             if location is None: | ||||
|                 location = 'http://packages.python.org/%s/' % name | ||||
|             logger.info('Upload successful. Visit %s', location) | ||||
|         else: | ||||
|             logger.error('Upload failed (%s): %s', r.status, r.reason) | ||||
| 
 | ||||
|         if self.show_response and logger.isEnabledFor(logging.INFO): | ||||
|             sep = '-' * 75 | ||||
|             logger.info('%s\n%s\n%s', sep, r.read().decode('utf-8'), sep) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tarek Ziade
						Tarek Ziade