diff --git a/spacy/displacy/__init__.py b/spacy/displacy/__init__.py index 7ce8e7289..cc4d439ee 100644 --- a/spacy/displacy/__init__.py +++ b/spacy/displacy/__init__.py @@ -75,14 +75,14 @@ def render( def serve( - docs: Union[Iterable[Doc], Doc], - style: str = "dep", - page: bool = True, - minify: bool = False, - options: Dict[str, Any] = {}, - manual: bool = False, - port: int = 5000, - host: str = "0.0.0.0", + docs: Union[Iterable[Doc], Doc], + style: str = "dep", + page: bool = True, + minify: bool = False, + options: Dict[str, Any] = {}, + manual: bool = False, + port: int = 5000, + host: str = "0.0.0.0", ) -> None: """Serve displaCy visualisation. @@ -100,22 +100,28 @@ def serve( """ from wsgiref import simple_server - if is_port_in_use(port): - port += 1 - while is_port_in_use(port) and port < 65535: - port += 1 + # automatically switch to the next available port if the default / given port is taken + available_port = port + while is_port_in_use(available_port) and available_port <= 65535: + available_port += 1 if is_in_jupyter(): warnings.warn(Warnings.W011) render(docs, style=style, page=page, minify=minify, options=options, manual=manual) - httpd = simple_server.make_server(host, port, app) + if port > 65535: + raise ValueError(Errors.E1048.format(host=host)) + + if available_port != port: + warnings.warn(Warnings.W124.format(host=host, port=port, available_port=available_port)) + + httpd = simple_server.make_server(host, available_port, app) print(f"\nUsing the '{style}' visualizer") - print(f"Serving on http://{host}:{port} ...\n") + print(f"Serving on http://{host}:{available_port} ...\n") try: httpd.serve_forever() except KeyboardInterrupt: - print(f"Shutting down server on port {port}.") + print(f"Shutting down server on port {available_port}.") finally: httpd.server_close() diff --git a/spacy/errors.py b/spacy/errors.py index e34614b0f..f5702056a 100644 --- a/spacy/errors.py +++ b/spacy/errors.py @@ -214,6 +214,7 @@ class Warnings(metaclass=ErrorsWithCodes): "is a Cython extension type.") W123 = ("Argument `enable` with value {enable} does not contain all values specified in the config option " "`enabled` ({enabled}). Be aware that this might affect other components in your pipeline.") + W124 = ("{host}:{port} is already in use, using the nearest available port {available_port} as an alternative.") class Errors(metaclass=ErrorsWithCodes): @@ -957,6 +958,7 @@ class Errors(metaclass=ErrorsWithCodes): E1046 = ("{cls_name} is an abstract class and cannot be instantiated. If you are looking for spaCy's default " "knowledge base, use `InMemoryLookupKB`.") E1047 = ("`find_threshold()` only supports components with a `scorer` attribute.") + E1048 = ("No port available for displacy on host {host}. Please specify a port by `displacy.serve(doc, port)`.") # Deprecated model shortcuts, only used in errors and warnings diff --git a/spacy/util.py b/spacy/util.py index 6d6fc494c..b3c9fae9d 100644 --- a/spacy/util.py +++ b/spacy/util.py @@ -1740,4 +1740,4 @@ def all_equal(iterable): def is_port_in_use(port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - return s.connect_ex(('localhost', port)) == 0 \ No newline at end of file + return s.connect_ex(('localhost', port)) == 0