import socket
import ssl
__version__ = "0.2.0"
[docs]def check_pypi_name(pypi_package_name, pypi_registry_host=None):
"""
Check if a package name exists on pypi.
TODO: Document the Registry URL construction.
It may not be obvious how pypi_package_name and pypi_registry_host are used
I'm appending the simple HTTP API parts of the registry standard specification.
It will return True if the package name, or any equivalent variation as defined by PEP 503 normalisation
rules (https://www.python.org/dev/peps/pep-0503/#normalized-names) is registered in the PyPI registry.
>>> check_pypi_name('pip')
True
>>> check_pypi_name('Pip')
True
It will return False if the package name, or any equivalent variation as defined by PEP 503 normalisation
rules (https://www.python.org/dev/peps/pep-0503/#normalized-names) is not registered in the PyPI registry.
>>> check_pypi_name('testy_mc-test_case-has.a.cousin_who_should_never_write_a_package')
False
:param pypi_package_name:
:param pypi_registry_host:
:return:
"""
if pypi_registry_host is None:
pypi_registry_host = 'pypi.python.org'
# Just a helpful reminder why this bytearray size was chosen.
# HTTP/1.1 200 OK
# HTTP/1.1 404 Not Found
receive_buffer = bytearray(b'------------')
context = ssl.create_default_context()
ssl_http_socket = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=pypi_registry_host)
ssl_http_socket.connect((pypi_registry_host, 443))
ssl_http_socket.send(b''.join([
b"HEAD /simple/", pypi_package_name.encode('ascii'), b"/ HTTP/1.0", b"\r\n",
b"Host: ", pypi_registry_host.encode('ascii'), b"\r\n",
b"\r\n\r\n"
]))
ssl_http_socket.recv_into(receive_buffer)
ssl_http_socket.shutdown(1)
ssl_http_socket.close()
# Early return when possible.
if b'HTTP/1.1 200' in receive_buffer:
return True
elif b'HTTP/1.1 404' in receive_buffer:
return False
remaining_bytes = ssl_http_socket.recv(1024)
redirect_path_location_start = remaining_bytes.find(b'Location:') + 10
redirect_path_location_end = remaining_bytes.find(b'\r\n', redirect_path_location_start)
# Append the trailing slash to avoid a needless extra redirect.
redirect_path = remaining_bytes[redirect_path_location_start:redirect_path_location_end] + b'/'
ssl_http_socket.shutdown(1)
ssl_http_socket.close()
ssl_http_socket = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=pypi_registry_host)
ssl_http_socket.connect((pypi_registry_host, 443))
ssl_http_socket.send(b''.join([
b"HEAD ", redirect_path, b" HTTP/1.0", b"\r\n",
b"Host: ", pypi_registry_host.encode('ascii'), b"\r\n",
b"\r\n\r\n"]))
ssl_http_socket.recv_into(receive_buffer)
if b'HTTP/1.1 200' in receive_buffer:
return True
elif b'HTTP/1.1 404' in receive_buffer:
return False
else:
NotImplementedError('A definitive answer was not found by primary or secondary lookups.')