Skip to content

Commit 1c0f1c7

Browse files
humitosstsewd
andauthored
Build: remove append_conf _magic_ from MkDocs (#11206)
* Build: remove `append_conf` _magic_ from MkDocs Delete all the magic around MkDocs YAML that processes the `mkdocs.yml`. file and define `readthedocs` theme automatically based on a feature flag, `MKDOCS_THEME_RTD`. This PR removes: - automatically defining `readthedocs` as theme when `MKDOCS_THEME_RTD` feature flag is defined. - processing `mkdocs.yml` to add internal JS and CSS (embed and analytics) files automatically This is another step forward on removing all the magic we perform on behalf the users and being more explicit about how to configure each doctool. Reference: * readthedocs/addons#72 (comment) Closes #8529 * Enable Addons if project is MkDocs Listen to `Project.post_save` signal and enable Addons if the project was created after a specific date, it's MkDocs and it's the first time the `AddonsConfig` is created. * Listen to Version instead * Typo * Update readthedocs/projects/signals.py Co-authored-by: Santos Gallegos <[email protected]> * Remove datetime restriction --------- Co-authored-by: Santos Gallegos <[email protected]>
1 parent d7f3b08 commit 1c0f1c7

File tree

7 files changed

+57
-754
lines changed

7 files changed

+57
-754
lines changed

readthedocs/config/tests/test_yaml_loader.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
from readthedocs.doc_builder.backends.mkdocs import (
2-
ProxyPythonName,
3-
yaml_dump_safely,
4-
yaml_load_safely,
5-
)
1+
from readthedocs.doc_builder.backends.mkdocs import ProxyPythonName, yaml_load_safely
62

73
content = """
84
int: 3
@@ -30,8 +26,3 @@ def test_yaml_load_safely():
3026
assert type(data["other_function"]) is ProxyPythonName
3127
assert data["function"].value == "python_function"
3228
assert data["other_function"].value == "module.other.function"
33-
34-
35-
def test_yaml_dump_safely():
36-
data = yaml_load_safely(content)
37-
assert yaml_load_safely(yaml_dump_safely(data)) == data

readthedocs/doc_builder/backends/mkdocs.py

+15-231
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,10 @@
99
import structlog
1010
import yaml
1111
from django.conf import settings
12-
from django.template import loader as template_loader
1312

1413
from readthedocs.core.utils.filesystem import safe_open
1514
from readthedocs.doc_builder.base import BaseBuilder
16-
from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError
1715
from readthedocs.projects.constants import MKDOCS, MKDOCS_HTML
18-
from readthedocs.projects.exceptions import UserFileNotFound
19-
from readthedocs.projects.models import Feature
2016

2117
log = structlog.get_logger(__name__)
2218

@@ -49,31 +45,23 @@ def __init__(self, *args, **kwargs):
4945
# This is the *MkDocs* yaml file
5046
self.yaml_file = self.get_yaml_config()
5147

52-
# README: historically, the default theme was ``readthedocs`` but in
53-
# https://github.com/rtfd/readthedocs.org/pull/4556 we change it to
54-
# ``mkdocs`` to maintain the same behavior in Read the Docs than
55-
# building locally. Although, we can't apply this into the Corporate
56-
# site. To keep the same default theme there, we created a Feature flag
57-
# for these project that were building with MkDocs in the Corporate
58-
# site.
59-
if self.project.has_feature(Feature.MKDOCS_THEME_RTD):
60-
self.DEFAULT_THEME_NAME = "readthedocs"
61-
log.warning(
62-
"Project using readthedocs theme as default for MkDocs.",
63-
project_slug=self.project.slug,
64-
)
65-
else:
66-
self.DEFAULT_THEME_NAME = "mkdocs"
67-
6848
def get_final_doctype(self):
6949
"""
7050
Select a doctype based on the ``use_directory_urls`` setting.
7151
7252
https://www.mkdocs.org/user-guide/configuration/#use_directory_urls
7353
"""
54+
55+
# TODO: we should eventually remove this method completely and stop
56+
# relying on "loading the `mkdocs.yml` file in a safe way just to know
57+
# if it's a MKDOCS or MKDOCS_HTML documentation type".
58+
7459
# Allow symlinks, but only the ones that resolve inside the base directory.
7560
with safe_open(
76-
self.yaml_file, "r", allow_symlinks=True, base_path=self.project_path
61+
self.yaml_file,
62+
"r",
63+
allow_symlinks=True,
64+
base_path=self.project_path,
7765
) as fh:
7866
config = yaml_load_safely(fh)
7967
use_directory_urls = config.get("use_directory_urls", True)
@@ -89,192 +77,23 @@ def get_yaml_config(self):
8977
mkdocs_path,
9078
)
9179

92-
def load_yaml_config(self):
93-
"""
94-
Load a YAML config.
95-
96-
:raises: ``MkDocsYAMLParseError`` if failed due to syntax errors.
97-
"""
98-
try:
99-
# Allow symlinks, but only the ones that resolve inside the base directory.
100-
result = safe_open(
101-
self.yaml_file, "r", allow_symlinks=True, base_path=self.project_path
102-
)
103-
if not result:
104-
raise UserFileNotFound(
105-
message_id=UserFileNotFound.FILE_NOT_FOUND,
106-
format_values={
107-
"filename": self.yaml_file,
108-
},
109-
)
110-
111-
config = yaml_load_safely(result)
112-
113-
if not config:
114-
raise MkDocsYAMLParseError(MkDocsYAMLParseError.EMPTY_CONFIG)
115-
if not isinstance(config, dict):
116-
raise MkDocsYAMLParseError(MkDocsYAMLParseError.CONFIG_NOT_DICT)
117-
return config
118-
119-
except IOError:
120-
raise MkDocsYAMLParseError(MkDocsYAMLParseError.NOT_FOUND)
121-
except yaml.YAMLError as exc:
122-
note = ""
123-
if hasattr(exc, "problem_mark"):
124-
mark = exc.problem_mark
125-
note = " (line %d, column %d)" % (
126-
mark.line + 1,
127-
mark.column + 1,
128-
)
129-
raise MkDocsYAMLParseError(
130-
MkDocsYAMLParseError.SYNTAX_ERROR,
131-
) from exc
132-
13380
def append_conf(self):
13481
"""
135-
Set mkdocs config values.
82+
Call `cat mkdocs.yaml` only.
13683
137-
:raises: ``MkDocsYAMLParseError`` if failed due to known type errors
138-
(i.e. expecting a list and a string is found).
139-
"""
140-
user_config = self.load_yaml_config()
141-
142-
# Handle custom docs dirs
143-
docs_dir = user_config.get("docs_dir", "docs")
144-
if not isinstance(docs_dir, (type(None), str)):
145-
raise MkDocsYAMLParseError(
146-
MkDocsYAMLParseError.INVALID_DOCS_DIR_CONFIG,
147-
)
148-
149-
user_config["docs_dir"] = docs_dir
150-
static_url = self.project.proxied_static_path
151-
152-
# Set mkdocs config values.
153-
extra_assets = {
154-
"extra_javascript": [
155-
"readthedocs-data.js",
156-
f"{static_url}core/js/readthedocs-doc-embed.js",
157-
f"{static_url}javascript/readthedocs-analytics.js",
158-
],
159-
"extra_css": [
160-
f"{static_url}css/badge_only.css",
161-
f"{static_url}css/readthedocs-doc-embed.css",
162-
],
163-
}
164-
165-
for config, extras in extra_assets.items():
166-
value = user_config.get(config, [])
167-
if value is None:
168-
value = []
169-
if not isinstance(value, list):
170-
raise MkDocsYAMLParseError(
171-
message_id=MkDocsYAMLParseError.INVALID_EXTRA_CONFIG,
172-
format_values={
173-
"extra_config": config,
174-
},
175-
)
176-
# Add the static file only if isn't already in the list.
177-
value.extend([extra for extra in extras if extra not in value])
178-
user_config[config] = value
179-
180-
# The docs path is relative to the location
181-
# of the mkdocs configuration file.
182-
docs_path = os.path.join(
183-
os.path.dirname(self.yaml_file),
184-
docs_dir,
185-
)
186-
187-
# if user puts an invalid `docs_dir` path raise an Exception
188-
if not os.path.exists(docs_path):
189-
raise MkDocsYAMLParseError(
190-
MkDocsYAMLParseError.INVALID_DOCS_DIR_PATH,
191-
)
192-
193-
# RTD javascript writing
194-
rtd_data = self.generate_rtd_data(
195-
docs_dir=os.path.relpath(docs_path, self.project_path),
196-
mkdocs_config=user_config,
197-
)
198-
with safe_open(
199-
os.path.join(docs_path, "readthedocs-data.js"), "w", encoding="utf-8"
200-
) as f:
201-
f.write(rtd_data)
202-
203-
# Use Read the Docs' analytics setup rather than mkdocs'
204-
# This supports using RTD's privacy improvements around analytics
205-
user_config["google_analytics"] = None
206-
207-
# README: make MkDocs to use ``readthedocs`` theme as default if the
208-
# user didn't specify a specific theme manually
209-
if self.project.has_feature(Feature.MKDOCS_THEME_RTD):
210-
if "theme" not in user_config:
211-
# mkdocs<0.17 syntax
212-
user_config["theme"] = self.DEFAULT_THEME_NAME
213-
214-
# Write the modified mkdocs configuration
215-
with safe_open(self.yaml_file, "w", encoding="utf-8") as f:
216-
yaml_dump_safely(
217-
user_config,
218-
f,
219-
)
84+
This behavior has changed. We used to parse the YAML file and append
85+
some configs automatically, but we have been removing that magic from
86+
our builders as much as we can.
22087
88+
This method will eventually removed completely.
89+
"""
22190
# Write the mkdocs.yml to the build logs
22291
self.run(
22392
"cat",
22493
os.path.relpath(self.yaml_file, self.project_path),
22594
cwd=self.project_path,
22695
)
22796

228-
def generate_rtd_data(self, docs_dir, mkdocs_config):
229-
"""Generate template properties and render readthedocs-data.js."""
230-
# Use the analytics code from mkdocs.yml
231-
# if it isn't set already by Read the Docs,
232-
analytics_code = self.version.project.analytics_code
233-
if not analytics_code and mkdocs_config.get("google_analytics"):
234-
# http://www.mkdocs.org/user-guide/configuration/#google_analytics
235-
analytics_code = mkdocs_config["google_analytics"][0]
236-
237-
commit = (
238-
self.version.project.vcs_repo(
239-
version=self.version.slug,
240-
environment=self.build_env,
241-
).commit,
242-
)
243-
244-
# Will be available in the JavaScript as READTHEDOCS_DATA.
245-
readthedocs_data = {
246-
"project": self.version.project.slug,
247-
"version": self.version.slug,
248-
"language": self.version.project.language,
249-
"programming_language": self.version.project.programming_language,
250-
"page": None,
251-
"theme": self.get_theme_name(mkdocs_config),
252-
"builder": "mkdocs",
253-
"docroot": docs_dir,
254-
"source_suffix": ".md",
255-
"api_host": settings.PUBLIC_API_URL,
256-
"ad_free": not self.project.show_advertising,
257-
"commit": commit,
258-
"global_analytics_code": (
259-
None
260-
if self.project.analytics_disabled
261-
else settings.GLOBAL_ANALYTICS_CODE
262-
),
263-
"user_analytics_code": analytics_code,
264-
"proxied_static_path": self.project.proxied_static_path,
265-
"proxied_api_host": self.project.proxied_api_host,
266-
}
267-
268-
data_ctx = {
269-
"readthedocs_data": readthedocs_data,
270-
"current_version": readthedocs_data["version"],
271-
"slug": readthedocs_data["project"],
272-
"html_theme": readthedocs_data["theme"],
273-
"pagename": None,
274-
}
275-
tmpl = template_loader.get_template("doc_builder/data.js.tmpl")
276-
return tmpl.render(data_ctx)
277-
27897
def build(self):
27998
build_command = [
28099
self.python_env.venv_bin(filename="python"),
@@ -296,42 +115,12 @@ def build(self):
296115
)
297116
return cmd_ret.successful
298117

299-
def get_theme_name(self, mkdocs_config):
300-
"""
301-
Get the theme configuration in the mkdocs_config.
302-
303-
In v0.17.0, the theme configuration switched
304-
from two separate configs (both optional) to a nested directive.
305-
306-
:see: http://www.mkdocs.org/about/release-notes/#theme-customization-1164
307-
:returns: the name of the theme RTD will use
308-
"""
309-
theme_setting = mkdocs_config.get("theme")
310-
if isinstance(theme_setting, dict):
311-
# Full nested theme config (the new configuration)
312-
return theme_setting.get("name") or self.DEFAULT_THEME_NAME
313-
314-
if theme_setting:
315-
# A string which is the name of the theme
316-
return theme_setting
317-
318-
theme_dir = mkdocs_config.get("theme_dir")
319-
if theme_dir:
320-
# Use the name of the directory in this project's custom theme directory
321-
return theme_dir.rstrip("/").split("/")[-1]
322-
323-
return self.DEFAULT_THEME_NAME
324-
325118

326119
class MkdocsHTML(BaseMkdocs):
327120
builder = "build"
328121
build_dir = "_readthedocs/html"
329122

330123

331-
# TODO: find a better way to integrate with MkDocs.
332-
# See https://github.com/readthedocs/readthedocs.org/issues/7844
333-
334-
335124
class ProxyPythonName(yaml.YAMLObject):
336125
def __init__(self, value):
337126
self.value = value
@@ -389,8 +178,3 @@ def yaml_load_safely(content):
389178
information loss.
390179
"""
391180
return yaml.load(content, Loader=SafeLoader)
392-
393-
394-
def yaml_dump_safely(content, stream=None):
395-
"""Uses ``SafeDumper`` dumper to write YAML contents."""
396-
return yaml.dump(content, stream=stream, Dumper=SafeDumper)

readthedocs/projects/apps.py

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class ProjectsConfig(AppConfig):
99
def ready(self):
1010
# Load and register notification messages for this application
1111
import readthedocs.projects.notifications # noqa
12+
import readthedocs.projects.signals # noqa
1213
import readthedocs.projects.tasks.builds # noqa
1314
import readthedocs.projects.tasks.search # noqa
1415
import readthedocs.projects.tasks.utils # noqa

readthedocs/projects/models.py

-5
Original file line numberDiff line numberDiff line change
@@ -1885,7 +1885,6 @@ def add_features(sender, **kwargs):
18851885

18861886
# Feature constants - this is not a exhaustive list of features, features
18871887
# may be added by other packages
1888-
MKDOCS_THEME_RTD = "mkdocs_theme_rtd"
18891888
API_LARGE_DATA = "api_large_data"
18901889
CONDA_APPEND_CORE_REQUIREMENTS = "conda_append_core_requirements"
18911890
ALL_VERSIONS_IN_HTML_CONTEXT = "all_versions_in_html_context"
@@ -1916,10 +1915,6 @@ def add_features(sender, **kwargs):
19161915
SCALE_IN_PROTECTION = "scale_in_prtection"
19171916

19181917
FEATURES = (
1919-
(
1920-
MKDOCS_THEME_RTD,
1921-
_("MkDocs: Use Read the Docs theme for MkDocs as default theme"),
1922-
),
19231918
(
19241919
API_LARGE_DATA,
19251920
_("Build: Try alternative method of posting large data"),

0 commit comments

Comments
 (0)