Skip to content

Commit

Permalink
bpo-31128: Allow pydoc to bind to arbitrary hostnames (#3011)
Browse files Browse the repository at this point in the history
New -n flag allow overriding localhost with custom value,
for example to run from containers.
  • Loading branch information
feanil authored and merwok committed Sep 14, 2017
1 parent ccb3c76 commit 6a396c9
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 19 deletions.
9 changes: 9 additions & 0 deletions Doc/library/pydoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ will start a HTTP server on port 1234, allowing you to browse the
documentation at ``http://localhost:1234/`` in your preferred Web browser.
Specifying ``0`` as the port number will select an arbitrary unused port.

:program:`pydoc -n <hostname>` will start the server listening at the given
hostname. By default the hostname is 'localhost' but if you want the server to
be reached from other machines, you may want to change the host name that the
server responds to. During development this is especially useful if you want
to run pydoc from within a container.

:program:`pydoc -b` will start the server and additionally open a web
browser to a module index page. Each served page has a navigation bar at the
top where you can *Get* help on an individual item, *Search* all modules with a
Expand Down Expand Up @@ -98,3 +104,6 @@ Reference Manual pages.
:mod:`pydoc` now uses :func:`inspect.signature` rather than
:func:`inspect.getfullargspec` to extract signature information from
callables.

.. versionchanged:: 3.7
Added the ``-n`` option.
43 changes: 26 additions & 17 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ class or function within a module or module in a package. If the
Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines
of all available modules.
Run "pydoc -n <hostname>" to start an HTTP server with the given
hostname (default: localhost) on the local machine.
Run "pydoc -p <port>" to start an HTTP server on the given port on the
local machine. Port number 0 can be used to get an arbitrary unused port.
Run "pydoc -b" to start an HTTP server on an arbitrary unused port and
open a Web browser to interactively browse documentation. The -p option
can be used with the -b option to explicitly specify the server port.
open a Web browser to interactively browse documentation. Combine with
the -n and -p options to control the hostname and port used.
Run "pydoc -w <name>" to write out the HTML documentation for a module
to a file named "<name>.html".
Expand Down Expand Up @@ -2162,7 +2165,7 @@ def onerror(modname):

# --------------------------------------- enhanced Web browser interface

def _start_server(urlhandler, port):
def _start_server(urlhandler, hostname, port):
"""Start an HTTP server thread on a specific port.
Start an HTML/text server thread, so HTML or text documents can be
Expand Down Expand Up @@ -2247,8 +2250,8 @@ def log_message(self, *args):

class DocServer(http.server.HTTPServer):

def __init__(self, port, callback):
self.host = 'localhost'
def __init__(self, host, port, callback):
self.host = host
self.address = (self.host, port)
self.callback = callback
self.base.__init__(self, self.address, self.handler)
Expand All @@ -2268,8 +2271,9 @@ def server_activate(self):

class ServerThread(threading.Thread):

def __init__(self, urlhandler, port):
def __init__(self, urlhandler, host, port):
self.urlhandler = urlhandler
self.host = host
self.port = int(port)
threading.Thread.__init__(self)
self.serving = False
Expand All @@ -2282,7 +2286,7 @@ def run(self):
DocServer.handler = DocHandler
DocHandler.MessageClass = email.message.Message
DocHandler.urlhandler = staticmethod(self.urlhandler)
docsvr = DocServer(self.port, self.ready)
docsvr = DocServer(self.host, self.port, self.ready)
self.docserver = docsvr
docsvr.serve_until_quit()
except Exception as e:
Expand All @@ -2304,7 +2308,7 @@ def stop(self):
self.serving = False
self.url = None

thread = ServerThread(urlhandler, port)
thread = ServerThread(urlhandler, hostname, port)
thread.start()
# Wait until thread.serving is True to make sure we are
# really up before returning.
Expand Down Expand Up @@ -2568,14 +2572,14 @@ def get_html_page(url):
raise TypeError('unknown content type %r for url %s' % (content_type, url))


def browse(port=0, *, open_browser=True):
def browse(port=0, *, open_browser=True, hostname='localhost'):
"""Start the enhanced pydoc Web server and open a Web browser.
Use port '0' to start the server on an arbitrary port.
Set open_browser to False to suppress opening a browser.
"""
import webbrowser
serverthread = _start_server(_url_handler, port)
serverthread = _start_server(_url_handler, hostname, port)
if serverthread.error:
print(serverthread.error)
return
Expand Down Expand Up @@ -2622,11 +2626,12 @@ class BadUsage(Exception): pass
sys.path.insert(0, '.')

try:
opts, args = getopt.getopt(sys.argv[1:], 'bk:p:w')
opts, args = getopt.getopt(sys.argv[1:], 'bk:n:p:w')
writing = False
start_server = False
open_browser = False
port = None
port = 0
hostname = 'localhost'
for opt, val in opts:
if opt == '-b':
start_server = True
Expand All @@ -2639,11 +2644,12 @@ class BadUsage(Exception): pass
port = val
if opt == '-w':
writing = True
if opt == '-n':
start_server = True
hostname = val

if start_server:
if port is None:
port = 0
browse(port, open_browser=open_browser)
browse(port, hostname=hostname, open_browser=open_browser)
return

if not args: raise BadUsage
Expand Down Expand Up @@ -2679,14 +2685,17 @@ class BadUsage(Exception): pass
{cmd} -k <keyword>
Search for a keyword in the synopsis lines of all available modules.
{cmd} -n <hostname>
Start an HTTP server with the given hostname (default: localhost).
{cmd} -p <port>
Start an HTTP server on the given port on the local machine. Port
number 0 can be used to get an arbitrary unused port.
{cmd} -b
Start an HTTP server on an arbitrary unused port and open a Web browser
to interactively browse documentation. The -p option can be used with
the -b option to explicitly specify the server port.
to interactively browse documentation. This option can be used in
combination with -n and/or -p.
{cmd} -w <name> ...
Write out the HTML documentation for a module to a file in the current
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -909,8 +909,8 @@ def my_url_handler(url, content_type):
text = 'the URL sent was: (%s, %s)' % (url, content_type)
return text

serverthread = pydoc._start_server(my_url_handler, port=0)
self.assertIn('localhost', serverthread.docserver.address)
serverthread = pydoc._start_server(my_url_handler, hostname='0.0.0.0', port=0)
self.assertIn('0.0.0.0', serverthread.docserver.address)

starttime = time.time()
timeout = 1 #seconds
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,7 @@ Claude Paroz
Heikki Partanen
Harri Pasanen
Gaël Pasgrimaud
Feanil Patel
Ashish Nitin Patil
Alecsandru Patrascu
Randy Pausch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow the pydoc server to bind to arbitrary hostnames.

0 comments on commit 6a396c9

Please sign in to comment.