mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 09:58:31 +07:00
Add Optional config but make fields in class-based config required
This commit is contained in:
@@ -138,6 +138,15 @@ class Config(UserDict):
|
||||
schema[attr_name] = attr
|
||||
cls._schema = tuple(schema.items())
|
||||
|
||||
for attr_name, attr in cls._schema:
|
||||
attr.required = True
|
||||
if getattr(attr, '_legacy_required', None) is not None:
|
||||
raise TypeError(
|
||||
f"{cls.__name__}.{attr_name}: "
|
||||
"Setting 'required' is unsupported in class-based configs. "
|
||||
"All values are required, or can be wrapped into config_options.Optional"
|
||||
)
|
||||
|
||||
def __new__(cls, *args, **kwargs) -> Config:
|
||||
"""Compatibility: allow referring to `LegacyConfig(...)` constructor as `Config(...)`."""
|
||||
if cls is Config:
|
||||
|
||||
@@ -9,7 +9,7 @@ import traceback
|
||||
import typing as t
|
||||
import warnings
|
||||
from collections import UserString
|
||||
from typing import Collection, Dict, Generic, List, NamedTuple, TypeVar
|
||||
from typing import Collection, Dict, Generic, List, NamedTuple, Tuple, TypeVar, Union, overload
|
||||
from urllib.parse import quote as urlquote
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
@@ -69,10 +69,19 @@ class OptionallyRequired(Generic[T], BaseConfigOption[T]):
|
||||
required values. It is a base class for config options.
|
||||
"""
|
||||
|
||||
def __init__(self, default=None, required: bool = False):
|
||||
@overload
|
||||
def __init__(self, default=None):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self, default=None, *, required: bool):
|
||||
...
|
||||
|
||||
def __init__(self, default=None, required=None):
|
||||
super().__init__()
|
||||
self.default = default
|
||||
self.required = required
|
||||
self._legacy_required = required
|
||||
self.required = bool(required)
|
||||
|
||||
def validate(self, value):
|
||||
"""
|
||||
@@ -100,7 +109,7 @@ class ListOfItems(Generic[T], BaseConfigOption[List[T]]):
|
||||
E.g. for `config_options.ListOfItems(config_options.Type(int))` a valid item is `[1, 2, 3]`.
|
||||
"""
|
||||
|
||||
required = False
|
||||
required: Union[bool, None] = None # Only for subclasses to set.
|
||||
|
||||
def __init__(self, option_type: BaseConfigOption[T], default: List[T] = []):
|
||||
super().__init__()
|
||||
@@ -152,9 +161,18 @@ class ConfigItems(ListOfItems[Config]):
|
||||
options.
|
||||
"""
|
||||
|
||||
def __init__(self, *config_options: PlainConfigSchemaItem, required: bool = False):
|
||||
@overload
|
||||
def __init__(self, *config_options: PlainConfigSchemaItem):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self, *config_options: PlainConfigSchemaItem, required: bool):
|
||||
...
|
||||
|
||||
def __init__(self, *config_options: PlainConfigSchemaItem, required=None):
|
||||
super().__init__(SubConfig(*config_options))
|
||||
self.required = required
|
||||
self._legacy_required = required
|
||||
self.required = bool(required)
|
||||
|
||||
|
||||
class Type(Generic[T], OptionallyRequired[T]):
|
||||
@@ -164,7 +182,15 @@ class Type(Generic[T], OptionallyRequired[T]):
|
||||
Validate the type of a config option against a given Python type.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self, type_: t.Type[T], length: t.Optional[int] = None, **kwargs):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self, type_: Tuple[t.Type[T], ...], length: t.Optional[int] = None, **kwargs):
|
||||
...
|
||||
|
||||
def __init__(self, type_, length=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._type = type_
|
||||
self.length = length
|
||||
@@ -336,9 +362,17 @@ class URL(OptionallyRequired[str]):
|
||||
Validate a URL by requiring a scheme is present.
|
||||
"""
|
||||
|
||||
def __init__(self, default='', required: bool = False, is_dir: bool = False):
|
||||
@overload
|
||||
def __init__(self, default='', *, is_dir: bool = False):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self, default='', *, required: bool, is_dir: bool = False):
|
||||
...
|
||||
|
||||
def __init__(self, default='', required=None, is_dir: bool = False):
|
||||
self.is_dir = is_dir
|
||||
super().__init__(default, required)
|
||||
super().__init__(default, required=required)
|
||||
|
||||
def run_validation(self, value):
|
||||
if value == '':
|
||||
@@ -357,6 +391,40 @@ class URL(OptionallyRequired[str]):
|
||||
raise ValidationError("The URL isn't valid, it should include the http:// (scheme)")
|
||||
|
||||
|
||||
class Optional(Generic[T], BaseConfigOption[Union[T, None]]):
|
||||
"""Wraps a field and makes a None value possible for it when no value is set.
|
||||
|
||||
E.g. `my_field = config_options.Optional(config_options.Type(str))`
|
||||
"""
|
||||
|
||||
def __init__(self, config_option: BaseConfigOption[T]):
|
||||
super().__init__()
|
||||
self.option = config_option
|
||||
self.warnings = config_option.warnings
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key in ('option', 'warnings'):
|
||||
raise AttributeError
|
||||
return getattr(self.option, key)
|
||||
|
||||
def pre_validation(self, config, key_name):
|
||||
return self.option.pre_validation(config, key_name)
|
||||
|
||||
def run_validation(self, value):
|
||||
if value is None:
|
||||
return self.default
|
||||
return self.option.validate(value)
|
||||
|
||||
def post_validation(self, config, key_name):
|
||||
result = self.option.post_validation(config, key_name)
|
||||
self.warnings = self.option.warnings
|
||||
return result
|
||||
|
||||
def reset_warnings(self):
|
||||
self.option.reset_warnings()
|
||||
self.warnings = self.option.warnings
|
||||
|
||||
|
||||
class RepoURL(URL):
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn(
|
||||
@@ -418,7 +486,7 @@ class EditURI(Type[str]):
|
||||
config[key_name] = edit_uri
|
||||
|
||||
|
||||
class EditURITemplate(OptionallyRequired[str]):
|
||||
class EditURITemplate(BaseConfigOption[str]):
|
||||
class Formatter(string.Formatter):
|
||||
def convert_field(self, value, conversion):
|
||||
if conversion == 'q':
|
||||
@@ -551,7 +619,15 @@ class ListOfPaths(ListOfItems[str]):
|
||||
config_options.ListOfItems(config_options.File(exists=True))
|
||||
"""
|
||||
|
||||
def __init__(self, default=[], required: bool = False):
|
||||
@overload
|
||||
def __init__(self, default=[]):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self, default=[], *, required: bool):
|
||||
...
|
||||
|
||||
def __init__(self, default=[], required=None):
|
||||
super().__init__(FilesystemObject(exists=True), default)
|
||||
self.required = required
|
||||
|
||||
@@ -696,7 +772,7 @@ class Nav(OptionallyRequired):
|
||||
return f"a {type(value).__name__}: {value!r}"
|
||||
|
||||
|
||||
class Private(OptionallyRequired):
|
||||
class Private(BaseConfigOption):
|
||||
"""
|
||||
Private Config Option
|
||||
|
||||
@@ -704,7 +780,8 @@ class Private(OptionallyRequired):
|
||||
"""
|
||||
|
||||
def run_validation(self, value):
|
||||
raise ValidationError('For internal use only.')
|
||||
if value is not None:
|
||||
raise ValidationError('For internal use only.')
|
||||
|
||||
|
||||
class MarkdownExtensions(OptionallyRequired[List[str]]):
|
||||
|
||||
@@ -14,23 +14,23 @@ def get_schema() -> base.PlainConfigSchema:
|
||||
class MkDocsConfig(base.Config):
|
||||
"""The configuration of MkDocs itself (the root object of mkdocs.yml)."""
|
||||
|
||||
config_file_path: str = c.Type(str) # type: ignore[assignment]
|
||||
config_file_path: str = c.Optional(c.Type(str)) # type: ignore[assignment]
|
||||
"""Reserved for internal use, stores the mkdocs.yml config file."""
|
||||
|
||||
site_name = c.Type(str, required=True)
|
||||
site_name = c.Type(str)
|
||||
"""The title to use for the documentation."""
|
||||
|
||||
nav = c.Nav()
|
||||
nav = c.Optional(c.Nav())
|
||||
"""Defines the structure of the navigation."""
|
||||
pages = c.Deprecated(removed=True, moved_to='nav')
|
||||
|
||||
site_url = c.URL(is_dir=True)
|
||||
site_url = c.Optional(c.URL(is_dir=True))
|
||||
"""The full URL to where the documentation will be hosted."""
|
||||
|
||||
site_description = c.Type(str)
|
||||
site_description = c.Optional(c.Type(str))
|
||||
"""A description for the documentation project that will be added to the
|
||||
HTML meta tags."""
|
||||
site_author = c.Type(str)
|
||||
site_author = c.Optional(c.Type(str))
|
||||
"""The name of the author to add to the HTML meta tags."""
|
||||
|
||||
theme = c.Theme(default='mkdocs')
|
||||
@@ -42,7 +42,7 @@ class MkDocsConfig(base.Config):
|
||||
site_dir = c.SiteDir(default='site')
|
||||
"""The directory where the site will be built to"""
|
||||
|
||||
copyright = c.Type(str)
|
||||
copyright = c.Optional(c.Type(str))
|
||||
"""A copyright notice to add to the footer of documentation."""
|
||||
|
||||
google_analytics = c.Deprecated(
|
||||
@@ -66,18 +66,18 @@ class MkDocsConfig(base.Config):
|
||||
True generates nicer URLs, but False is useful if browsing the output on
|
||||
a filesystem."""
|
||||
|
||||
repo_url = c.URL()
|
||||
repo_url = c.Optional(c.URL())
|
||||
"""Specify a link to the project source repo to be included
|
||||
in the documentation pages."""
|
||||
|
||||
repo_name = c.RepoName('repo_url')
|
||||
repo_name = c.Optional(c.RepoName('repo_url'))
|
||||
"""A name to use for the link to the project source repo.
|
||||
Default, If repo_url is unset then None, otherwise
|
||||
"GitHub", "Bitbucket" or "GitLab" for known url or Hostname
|
||||
for unknown urls."""
|
||||
|
||||
edit_uri_template = c.EditURITemplate('edit_uri')
|
||||
edit_uri = c.EditURI('repo_url')
|
||||
edit_uri_template = c.Optional(c.EditURITemplate('edit_uri'))
|
||||
edit_uri = c.Optional(c.EditURI('repo_url'))
|
||||
"""Specify a URI to the docs dir in the project source repo, relative to the
|
||||
repo_url. When set, a link directly to the page in the source repo will
|
||||
be added to the generated HTML. If repo_url is not set also, this option
|
||||
|
||||
@@ -47,7 +47,7 @@ class LangOption(c.OptionallyRequired[List[str]]):
|
||||
|
||||
|
||||
class _PluginConfig(base.Config):
|
||||
lang = LangOption()
|
||||
lang = c.Optional(LangOption())
|
||||
separator = c.Type(str, default=r'[\s\-]+')
|
||||
min_search_length = c.Type(int, default=3)
|
||||
prebuild_index = c.Choice((False, True, 'node', 'python'), default=False)
|
||||
|
||||
@@ -17,7 +17,7 @@ from mkdocs.tests.base import load_config
|
||||
class _DummyPluginConfig(base.Config):
|
||||
foo = c.Type(str, default='default foo')
|
||||
bar = c.Type(int, default=0)
|
||||
dir = c.Dir(exists=False)
|
||||
dir = c.Optional(c.Dir(exists=False))
|
||||
|
||||
|
||||
class DummyPlugin(plugins.BasePlugin[_DummyPluginConfig]):
|
||||
|
||||
Reference in New Issue
Block a user