Refactor serve URL handling, pass only the URL to build (#3501)

Co-authored-by: Philipp Temminghoff <philipptemminghoff@googlemail.com>
This commit is contained in:
Oleh Prypin
2023-12-03 21:59:46 +01:00
committed by GitHub
parent 646987da45
commit 8035d78dad
5 changed files with 41 additions and 44 deletions

View File

@@ -22,8 +22,6 @@ from mkdocs.utils import templates
if TYPE_CHECKING:
from mkdocs.config.defaults import MkDocsConfig
if TYPE_CHECKING:
from mkdocs.livereload import LiveReloadServer
log = logging.getLogger(__name__)
@@ -247,9 +245,7 @@ def _build_page(
config._current_page = None
def build(
config: MkDocsConfig, live_server: LiveReloadServer | None = None, dirty: bool = False
) -> None:
def build(config: MkDocsConfig, *, serve_url: str | None = None, dirty: bool = False) -> None:
"""Perform a full site build."""
logger = logging.getLogger('mkdocs')
@@ -259,7 +255,7 @@ def build(
if config.strict:
logging.getLogger('mkdocs').addHandler(warning_counter)
inclusion = InclusionLevel.all if live_server else InclusionLevel.is_included
inclusion = InclusionLevel.all if serve_url else InclusionLevel.is_included
try:
start = time.monotonic()
@@ -280,7 +276,7 @@ def build(
" links within your site. This option is designed for site development purposes only."
)
if not live_server: # pragma: no cover
if not serve_url: # pragma: no cover
log.info(f"Building documentation to directory: {config.site_dir}")
if dirty and site_directory_contains_stale_files(config.site_dir):
log.info("The directory contains stale files. Use --clean to remove them.")
@@ -306,8 +302,8 @@ def build(
for file in files.documentation_pages(inclusion=inclusion):
log.debug(f"Reading: {file.src_uri}")
if file.page is None and file.inclusion.is_not_in_nav():
if live_server and file.inclusion.is_excluded():
excluded.append(urljoin(live_server.url, file.url))
if serve_url and file.inclusion.is_excluded():
excluded.append(urljoin(serve_url, file.url))
Page(None, file, config)
assert file.page is not None
_populate_page(file.page, config, files, dirty)

View File

@@ -9,7 +9,7 @@ from urllib.parse import urlsplit
from mkdocs.commands.build import build
from mkdocs.config import load_config
from mkdocs.livereload import LiveReloadServer
from mkdocs.livereload import LiveReloadServer, _serve_url
if TYPE_CHECKING:
from mkdocs.config.defaults import MkDocsConfig
@@ -37,9 +37,6 @@ def serve(
# string is returned. And it makes MkDocs temp dirs easier to identify.
site_dir = tempfile.mkdtemp(prefix='mkdocs_')
def mount_path(config: MkDocsConfig):
return urlsplit(config.site_url or '/').path
def get_config():
config = load_config(
config_file=config_file,
@@ -47,7 +44,6 @@ def serve(
**kwargs,
)
config.watch.extend(watch)
config.site_url = f'http://{config.dev_addr}{mount_path(config)}'
return config
is_clean = build_type == 'clean'
@@ -56,16 +52,20 @@ def serve(
config = get_config()
config.plugins.on_startup(command=('build' if is_clean else 'serve'), dirty=is_dirty)
host, port = config.dev_addr
mount_path = urlsplit(config.site_url or '/').path
config.site_url = serve_url = _serve_url(host, port, mount_path)
def builder(config: MkDocsConfig | None = None):
log.info("Building documentation...")
if config is None:
config = get_config()
config.site_url = serve_url
build(config, live_server=None if is_clean else server, dirty=is_dirty)
build(config, serve_url=None if is_clean else serve_url, dirty=is_dirty)
host, port = config.dev_addr
server = LiveReloadServer(
builder=builder, host=host, port=port, root=site_dir, mount_path=mount_path(config)
builder=builder, host=host, port=port, root=site_dir, mount_path=mount_path
)
def error_handler(code) -> bytes | None:

View File

@@ -81,6 +81,15 @@ class _LoggerAdapter(logging.LoggerAdapter):
log = _LoggerAdapter(logging.getLogger(__name__), {})
def _normalize_mount_path(mount_path: str) -> str:
"""Ensure the mount path starts and ends with a slash."""
return ("/" + mount_path.lstrip("/")).rstrip("/") + "/"
def _serve_url(host: str, port: int, path: str) -> str:
return f"http://{host}:{port}{_normalize_mount_path(path)}"
class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGIServer):
daemon_threads = True
poll_response_timeout = 60
@@ -96,16 +105,14 @@ class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGISe
shutdown_delay: float = 0.25,
) -> None:
self.builder = builder
self.server_name = host
self.server_port = port
try:
if isinstance(ipaddress.ip_address(host), ipaddress.IPv6Address):
self.address_family = socket.AF_INET6
except Exception:
pass
self.root = os.path.abspath(root)
self.mount_path = ("/" + mount_path.lstrip("/")).rstrip("/") + "/"
self.url = f"http://{self.server_name}:{self.server_port}{self.mount_path}"
self.mount_path = _normalize_mount_path(mount_path)
self.url = _serve_url(host, port, mount_path)
self.build_delay = 0.1
self.shutdown_delay = shutdown_delay
# To allow custom error pages.

View File

@@ -16,7 +16,6 @@ import markdown.preprocessors
from mkdocs.commands import build
from mkdocs.config import base
from mkdocs.exceptions import PluginError
from mkdocs.livereload import LiveReloadServer
from mkdocs.structure.files import File, Files
from mkdocs.structure.nav import get_navigation
from mkdocs.structure.pages import Page
@@ -36,13 +35,6 @@ def build_page(title, path, config, md_src=''):
return page, files
def testing_server(root, builder=lambda: None, mount_path="/"):
with mock.patch("socket.socket"):
return LiveReloadServer(
builder, host="localhost", port=123, root=root, mount_path=mount_path
)
class BuildTests(PathAssertionMixin, unittest.TestCase):
def _get_env_with_null_translations(self, config):
env = config.theme.get_env()
@@ -596,7 +588,7 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
exclude_docs='ba*.md',
)
with self.subTest(live_server=None):
with self.subTest(serve_url=None):
expected_logs = '''
INFO:Doc file 'test/foo.md' contains a link to 'test/bar.md' which is excluded from the built site.
'''
@@ -606,8 +598,8 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
self.assertPathNotExists(site_dir, 'test', 'baz.html')
self.assertPathNotExists(site_dir, '.zoo.html')
server = testing_server(site_dir, mount_path='/documentation/')
with self.subTest(live_server=server):
serve_url = 'http://localhost:123/documentation/'
with self.subTest(serve_url=serve_url):
expected_logs = '''
INFO:Doc file 'test/bar.md' contains a relative link 'nonexistent.md', but the target 'test/nonexistent.md' is not found among documentation files.
INFO:Doc file 'test/foo.md' contains a link to 'test/bar.md' which is excluded from the built site.
@@ -617,7 +609,7 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
- http://localhost:123/documentation/test/baz.html
'''
with self._assert_build_logs(expected_logs):
build.build(cfg, live_server=server)
build.build(cfg, serve_url=serve_url)
foo_path = Path(site_dir, 'test', 'foo.html')
self.assertTrue(foo_path.is_file())
@@ -639,13 +631,13 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
def test_conflicting_readme_and_index(self, site_dir, docs_dir):
cfg = load_config(docs_dir=docs_dir, site_dir=site_dir, use_directory_urls=False)
for server in None, testing_server(site_dir):
with self.subTest(live_server=server):
for serve_url in None, 'http://localhost:123/':
with self.subTest(serve_url=serve_url):
expected_logs = '''
WARNING:Excluding 'foo/README.md' from the site because it conflicts with 'foo/index.md'.
'''
with self._assert_build_logs(expected_logs):
build.build(cfg, live_server=server)
build.build(cfg, serve_url=serve_url)
index_path = Path(site_dir, 'foo', 'index.html')
self.assertPathIsFile(index_path)
@@ -663,10 +655,10 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
docs_dir=docs_dir, site_dir=site_dir, use_directory_urls=False, exclude_docs='index.md'
)
for server in None, testing_server(site_dir):
with self.subTest(live_server=server):
for serve_url in None, 'http://localhost:123/':
with self.subTest(serve_url=serve_url):
with self._assert_build_logs(''):
build.build(cfg, live_server=server)
build.build(cfg, serve_url=serve_url)
index_path = Path(site_dir, 'foo', 'index.html')
self.assertPathIsFile(index_path)
@@ -693,9 +685,9 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
assert f is not None
config.nav = Path(f.abs_src_path).read_text().splitlines()
for server in None, testing_server(site_dir):
for serve_url in None, 'http://localhost:123/':
for exclude in 'full', 'nav', None:
with self.subTest(live_server=server, exclude=exclude):
with self.subTest(serve_url=serve_url, exclude=exclude):
cfg = load_config(
docs_dir=docs_dir,
site_dir=site_dir,
@@ -711,13 +703,13 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
INFO:The following pages exist in the docs directory, but are not included in the "nav" configuration:
- SUMMARY.md
'''
if exclude == 'full' and server:
if exclude == 'full' and serve_url:
expected_logs = '''
INFO:The following pages are being built only for the preview but will be excluded from `mkdocs build` per `exclude_docs`:
- http://localhost:123/SUMMARY.html
'''
with self._assert_build_logs(expected_logs):
build.build(cfg, live_server=server)
build.build(cfg, serve_url=serve_url)
foo_path = Path(site_dir, 'foo.html')
self.assertPathIsFile(foo_path)
@@ -727,7 +719,7 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
)
summary_path = Path(site_dir, 'SUMMARY.html')
if exclude == 'full' and not server:
if exclude == 'full' and not serve_url:
self.assertPathNotExists(summary_path)
else:
self.assertPathExists(summary_path)

View File

@@ -39,6 +39,8 @@ def testing_server(root, builder=lambda: None, mount_path="/"):
mount_path=mount_path,
polling_interval=0.2,
)
server.server_name = "localhost"
server.server_port = 0
server.setup_environ()
server.observer.start()
thread = threading.Thread(target=server._build_loop, daemon=True)