diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py index b8b59831e3..c6bc97cc87 100644 --- a/mkdocs/tests/config/config_options_tests.py +++ b/mkdocs/tests/config/config_options_tests.py @@ -3,14 +3,25 @@ import sys import textwrap import unittest +from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar from unittest.mock import patch +if TYPE_CHECKING: + from typing_extensions import assert_type +else: + + def assert_type(val, typ): + return None + + import mkdocs from mkdocs.config import config_options -from mkdocs.config.base import Config, ValidationError +from mkdocs.config.base import Config, LegacyConfig, ValidationError from mkdocs.tests.base import tempdir from mkdocs.utils import yaml_load +SomeConfig = TypeVar('SomeConfig', bound=Config) + class UnexpectedError(Exception): pass @@ -24,8 +35,10 @@ def expect_error(self, **kwargs): yield self.assertEqual(f'{key}="{msg}"', str(cm.exception)) - def get_config(self, schema, cfg, warnings={}): - config = Config(schema) + def get_config( + self, config_class: Type[SomeConfig], cfg: Dict[str, Any], warnings={} + ) -> SomeConfig: + config = config_class() config.load_dict(cfg) actual_errors, actual_warnings = config.validate() if actual_errors: @@ -34,92 +47,95 @@ def get_config(self, schema, cfg, warnings={}): return config -class OptionallyRequiredTest(unittest.TestCase): - def test_empty(self): - option = config_options.OptionallyRequired() - value = option.validate(None) - self.assertEqual(value, None) +class TypeTest(TestCase): + def test_single_type(self) -> None: + class Schema(Config): + option = config_options.Type(str) - self.assertEqual(option.required, False) + conf = self.get_config(Schema, {"option": "Testing"}) + assert_type(conf.option, str) + self.assertEqual(conf.option, "Testing") - def test_required(self): - option = config_options.OptionallyRequired(required=True) - with self.assertRaises(config_options.ValidationError): - option.validate(None) - - self.assertEqual(option.required, True) + def test_multiple_types(self) -> None: + class Schema(Config): + option = config_options.Type((list, tuple)) - def test_required_no_default(self): - option = config_options.OptionallyRequired(required=True) - value = option.validate(2) - self.assertEqual(2, value) + conf = self.get_config(Schema, {"option": [1, 2, 3]}) + self.assertEqual(conf.option, [1, 2, 3]) - def test_default(self): - option = config_options.OptionallyRequired(default=1) - value = option.validate(None) - self.assertEqual(1, value) + conf = self.get_config(Schema, {"option": (1, 2, 3)}) + self.assertEqual(conf.option, (1, 2, 3)) - def test_replace_default(self): - option = config_options.OptionallyRequired(default=1) - value = option.validate(2) - self.assertEqual(2, value) + with self.expect_error( + option="Expected type: (, ) but received: " + ): + self.get_config(Schema, {"option": {'a': 1}}) + def test_length(self): + option = config_options.Type(str, length=7) -class TypeTest(unittest.TestCase): - def test_single_type(self): - option = config_options.Type(str) value = option.validate("Testing") self.assertEqual(value, "Testing") - def test_multiple_types(self): - option = config_options.Type((list, tuple)) + with self.assertRaises(config_options.ValidationError): + option.validate("Testing Long") - value = option.validate([1, 2, 3]) - self.assertEqual(value, [1, 2, 3]) - value = option.validate((1, 2, 3)) - self.assertEqual(value, (1, 2, 3)) +class ChoiceTest(TestCase): + def test_valid_choice(self) -> None: + class Schema(Config): + option = config_options.Choice(('python', 'node')) - with self.assertRaises(config_options.ValidationError): - option.validate({'a': 1}) + conf = self.get_config(Schema, {'option': 'python'}) + assert_type(conf.option, str) + self.assertEqual(conf.option, 'python') - def test_length(self): - option = config_options.Type(str, length=7) + def test_optional(self) -> None: + class Schema(Config): + option = config_options.Optional(config_options.Choice(('python', 'node'))) - value = option.validate("Testing") - self.assertEqual(value, "Testing") + conf = self.get_config(Schema, {'option': 'python'}) + assert_type(conf.option, Optional[str]) + self.assertEqual(conf.option, 'python') - with self.assertRaises(config_options.ValidationError): - option.validate("Testing Long") + conf = self.get_config(Schema, {}) + self.assertEqual(conf.option, None) + def test_default(self) -> None: + class Schema(Config): + option = config_options.Choice(('a', 'b', 'c'), default='b') -class ChoiceTest(unittest.TestCase): - def test_valid_choice(self): - option = config_options.Choice(('python', 'node')) - value = option.validate('python') - self.assertEqual(value, 'python') + conf = self.get_config(Schema, {}) + assert_type(conf.option, str) + self.assertEqual(conf.option, 'b') - def test_default(self): - option = config_options.Choice(('python', 'node'), default='node') - value = option.validate(None) - self.assertEqual(value, 'node') + conf = self.get_config(Schema, {}) + assert_type(conf.option, str) + self.assertEqual(conf.option, 'b') + + with self.expect_error(option="Expected one of: ('a', 'b', 'c') but received: go"): + self.get_config(Schema, {'option': 'go'}) - def test_excluded_default(self): + def test_invalid_default(self): + with self.assertRaises(ValueError): + config_options.Choice(('a', 'b'), default='c') with self.assertRaises(ValueError): - config_options.Choice(('python', 'node'), default='a') + config_options.Choice(('a', 'b'), default='c', required=True) - def test_invalid_choice(self): - option = config_options.Choice(('python', 'node')) - with self.assertRaises(config_options.ValidationError): - option.validate('go') + def test_invalid_choice(self) -> None: + class Schema(Config): + option = config_options.Choice(('python', 'node')) + + with self.expect_error(option="Expected one of: ('python', 'node') but received: go"): + self.get_config(Schema, {'option': 'go'}) - def test_invalid_choices(self): + def test_invalid_choices(self) -> None: self.assertRaises(ValueError, config_options.Choice, '') self.assertRaises(ValueError, config_options.Choice, []) self.assertRaises(ValueError, config_options.Choice, 5) -class DeprecatedTest(unittest.TestCase): +class DeprecatedTest(TestCase): def test_deprecated_option_simple(self): option = config_options.Deprecated() option.pre_validation({'d': 'value'}, 'd') @@ -228,98 +244,116 @@ def test_deprecated_option_move_invalid(self): self.assertEqual(config, {'old': 'value', 'foo': 'wrong type'}) -class IpAddressTest(unittest.TestCase): - def test_valid_address(self): - addr = '127.0.0.1:8000' - - option = config_options.IpAddress() - value = option.validate(addr) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, '127.0.0.1') - self.assertEqual(value.port, 8000) - - def test_valid_IPv6_address(self): - addr = '::1:8000' - - option = config_options.IpAddress() - value = option.validate(addr) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, '::1') - self.assertEqual(value.port, 8000) - - def test_named_address(self): - addr = 'localhost:8000' - - option = config_options.IpAddress() - value = option.validate(addr) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, 'localhost') - self.assertEqual(value.port, 8000) - - def test_default_address(self): - addr = '127.0.0.1:8000' - - option = config_options.IpAddress(default=addr) - value = option.validate(None) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, '127.0.0.1') - self.assertEqual(value.port, 8000) +class IpAddressTest(TestCase): + def test_valid_address(self) -> None: + class Schema(Config): + option = config_options.IpAddress() + + conf = self.get_config(Schema, {'option': '127.0.0.1:8000'}) + + assert_type(conf.option, config_options._IpAddressValue) + assert conf.option is not None + assert_type(conf.option.host, str) + assert_type(conf.option.port, int) + self.assertEqual(str(conf.option), '127.0.0.1:8000') + self.assertEqual(conf.option.host, '127.0.0.1') + self.assertEqual(conf.option.port, 8000) + + conf = self.get_config(Schema, {'option': '::1:8000'}) + assert conf.option is not None + self.assertEqual(str(conf.option), '::1:8000') + self.assertEqual(conf.option.host, '::1') + self.assertEqual(conf.option.port, 8000) + + conf = self.get_config(Schema, {'option': 'localhost:8000'}) + assert conf.option is not None + self.assertEqual(str(conf.option), 'localhost:8000') + self.assertEqual(conf.option.host, 'localhost') + self.assertEqual(conf.option.port, 8000) + + def test_default_address(self) -> None: + class Schema(Config): + option = config_options.IpAddress(default='127.0.0.1:7000') + + conf = self.get_config(Schema, {}) + assert_type(conf.option.host, str) + assert_type(conf.option.port, int) + self.assertEqual(str(conf.option), '127.0.0.1:7000') + self.assertEqual(conf.option.host, '127.0.0.1') + self.assertEqual(conf.option.port, 7000) @unittest.skipIf( sys.version_info < (3, 9, 5), "Leading zeros allowed in IP addresses before Python3.9.5", ) - def test_invalid_leading_zeros(self): - addr = '127.000.000.001:8000' - option = config_options.IpAddress(default=addr) - with self.assertRaises(config_options.ValidationError): - option.validate(addr) + def test_invalid_leading_zeros(self) -> None: + class Schema(Config): + option = config_options.IpAddress() - def test_invalid_address_range(self): - option = config_options.IpAddress() - with self.assertRaises(config_options.ValidationError): - option.validate('277.0.0.1:8000') - - def test_invalid_address_format(self): - option = config_options.IpAddress() - with self.assertRaises(config_options.ValidationError): - option.validate('127.0.0.18000') - - def test_invalid_address_type(self): - option = config_options.IpAddress() - with self.assertRaises(config_options.ValidationError): - option.validate(123) + with self.expect_error( + option="'127.000.000.001' does not appear to be an IPv4 or IPv6 address" + ): + self.get_config(Schema, {'option': '127.000.000.001:8000'}) - def test_invalid_address_port(self): - option = config_options.IpAddress() - with self.assertRaises(config_options.ValidationError): - option.validate('127.0.0.1:foo') + def test_invalid_address(self) -> None: + class Schema(Config): + option = config_options.IpAddress() - def test_invalid_address_missing_port(self): - option = config_options.IpAddress() - with self.assertRaises(config_options.ValidationError): - option.validate('127.0.0.1') - - def test_unsupported_address(self): - option = config_options.IpAddress() - value = option.validate('0.0.0.0:8000') - option.post_validation({'dev_addr': value}, 'dev_addr') - self.assertEqual(len(option.warnings), 1) - - def test_unsupported_IPv6_address(self): - option = config_options.IpAddress() - value = option.validate(':::8000') - option.post_validation({'dev_addr': value}, 'dev_addr') - self.assertEqual(len(option.warnings), 1) - - def test_invalid_IPv6_address(self): - # The server will error out with this so we treat it as invalid. - option = config_options.IpAddress() - with self.assertRaises(config_options.ValidationError): - option.validate('[::1]:8000') + for addr, error in [ + ( + '277.0.0.1:8000', + "'277.0.0.1' does not appear to be an IPv4 or IPv6 address", + ), + ( + '127.0.0.18000', + "Must be a string of format 'IP:PORT'", + ), + ( + 123, + "Must be a string of format 'IP:PORT'", + ), + ( + '127.0.0.1:foo', + "'foo' is not a valid port", + ), + ( + '127.0.0.1', + "Must be a string of format 'IP:PORT'", + ), + ( # The server will error out with this so we treat it as invalid. + '[::1]:8000', + "'[::1]' does not appear to be an IPv4 or IPv6 address", + ), + ]: + with self.subTest(addr): + with self.expect_error(option=error): + self.get_config(Schema, {'option': addr}) + + def test_warn_about_address(self): + class Schema(Config): + dev_addr = config_options.IpAddress() + + for addr, warning in [ + ( + '0.0.0.0:8000', + "The use of the IP address '0.0.0.0' suggests a production environment or " + "the use of a proxy to connect to the MkDocs server. However, the MkDocs' " + "server is intended for local development purposes only. Please use a third " + "party production-ready server instead.", + ), + ( + ':::8000', + "The use of the IP address '::' suggests a production environment or " + "the use of a proxy to connect to the MkDocs server. However, the MkDocs' " + "server is intended for local development purposes only. Please use a third " + "party production-ready server instead.", + ), + ]: + with self.subTest(addr): + self.get_config(Schema, {'dev_addr': addr}, warnings=dict(dev_addr=warning)) -class URLTest(unittest.TestCase): +class URLTest(TestCase): def test_valid_url(self): option = config_options.URL() @@ -350,7 +384,7 @@ def test_invalid_type(self): option.validate(1) -class RepoURLTest(unittest.TestCase): +class RepoURLTest(TestCase): def test_repo_name_github(self): option = config_options.RepoURL() config = {'repo_url': "https://github.com/mkdocs/mkdocs"} @@ -411,68 +445,70 @@ def test_repo_name_custom_and_empty_edit_uri(self): class ListOfItemsTest(TestCase): def test_int_type(self): - schema = [ - ('option', config_options.ListOfItems(config_options.Type(int))), - ] - cfg = self.get_config(schema, {'option': [1, 2, 3]}) + class Schema(Config): + option = config_options.ListOfItems(config_options.Type(int)) + + cfg = self.get_config(Schema, {'option': [1, 2, 3]}) self.assertEqual(cfg['option'], [1, 2, 3]) with self.expect_error( option="Expected type: but received: " ): - cfg = self.get_config(schema, {'option': [1, None, 3]}) + cfg = self.get_config(Schema, {'option': [1, None, 3]}) def test_combined_float_type(self): - schema = [ - ('option', config_options.ListOfItems(config_options.Type((int, float)))), - ] - cfg = self.get_config(schema, {'option': [1.4, 2, 3]}) + class Schema(Config): + option = config_options.ListOfItems(config_options.Type((int, float))) + + cfg = self.get_config(Schema, {'option': [1.4, 2, 3]}) self.assertEqual(cfg['option'], [1.4, 2, 3]) with self.expect_error( option="Expected type: (, ) but received: " ): - self.get_config(schema, {'option': ['a']}) + self.get_config(Schema, {'option': ['a']}) def test_list_default(self): - schema = [ - ('option', config_options.ListOfItems(config_options.Type(int))), - ] - cfg = self.get_config(schema, {}) + class Schema(Config): + option = config_options.Optional(config_options.ListOfItems(config_options.Type(int))) + + cfg = self.get_config(Schema, {}) self.assertEqual(cfg['option'], []) - cfg = self.get_config(schema, {'option': None}) + cfg = self.get_config(Schema, {'option': None}) self.assertEqual(cfg['option'], []) def test_none_default(self): - schema = [ - ('option', config_options.ListOfItems(config_options.Type(str), default=None)), - ] - cfg = self.get_config(schema, {}) + class Schema(Config): + option = config_options.Optional( + config_options.ListOfItems(config_options.Type(str), default=None) + ) + + cfg = self.get_config(Schema, {}) self.assertEqual(cfg['option'], None) - cfg = self.get_config(schema, {'option': None}) + cfg = self.get_config(Schema, {'option': None}) self.assertEqual(cfg['option'], None) - cfg = self.get_config(schema, {'option': ['foo']}) + cfg = self.get_config(Schema, {'option': ['foo']}) self.assertEqual(cfg['option'], ['foo']) def test_string_not_a_list_of_strings(self): - schema = [ - ('option', config_options.ListOfItems(config_options.Type(str))), - ] + class Schema(Config): + option = config_options.ListOfItems(config_options.Type(str)) + with self.expect_error(option="Expected a list of items, but a was given."): - self.get_config(schema, {'option': 'foo'}) + self.get_config(Schema, {'option': 'foo'}) def test_post_validation_error(self): - schema = [ - ('option', config_options.ListOfItems(config_options.IpAddress())), - ] + class Schema(Config): + option = config_options.ListOfItems(config_options.IpAddress()) + with self.expect_error(option="'asdf' is not a valid port"): - self.get_config(schema, {'option': ["localhost:8000", "1.2.3.4:asdf"]}) + self.get_config(Schema, {'option': ["localhost:8000", "1.2.3.4:asdf"]}) -class FilesystemObjectTest(unittest.TestCase): +class FilesystemObjectTest(TestCase): def test_valid_dir(self): for cls in config_options.Dir, config_options.FilesystemObject: with self.subTest(cls): @@ -529,7 +565,7 @@ def test_incorrect_type_error(self): def test_with_unicode(self): for cls in config_options.Dir, config_options.File, config_options.FilesystemObject: with self.subTest(cls): - cfg = Config( + cfg = LegacyConfig( [('dir', cls())], config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), ) @@ -543,7 +579,7 @@ def test_with_unicode(self): self.assertIsInstance(cfg['dir'], str) def test_dir_bytes(self): - cfg = Config( + cfg = LegacyConfig( [('dir', config_options.Dir())], config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), ) @@ -564,7 +600,7 @@ def test_config_dir_prepended(self): for cls in config_options.Dir, config_options.File, config_options.FilesystemObject: with self.subTest(cls): base_path = os.path.abspath('.') - cfg = Config( + cfg = LegacyConfig( [('dir', cls())], config_file_path=os.path.join(base_path, 'mkdocs.yml'), ) @@ -582,7 +618,7 @@ def test_config_dir_prepended(self): self.assertEqual(cfg['dir'], os.path.join(base_path, 'foo')) def test_site_dir_is_config_dir_fails(self): - cfg = Config( + cfg = LegacyConfig( [('dir', config_options.DocsDir())], config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), ) @@ -599,7 +635,7 @@ def test_site_dir_is_config_dir_fails(self): self.assertEqual(len(warns), 0) -class ListOfPathsTest(unittest.TestCase): +class ListOfPathsTest(TestCase): def test_valid_path(self): paths = [os.path.dirname(__file__)] option = config_options.ListOfPaths() @@ -618,9 +654,8 @@ def test_non_path(self): option.validate(paths) def test_empty_list(self): - paths = [] option = config_options.ListOfPaths() - option.validate(paths) + option.validate([]) def test_non_list(self): paths = os.path.dirname(__file__) @@ -635,7 +670,7 @@ def test_file(self): def test_paths_localized_to_config(self): base_path = os.path.abspath('.') - cfg = Config( + cfg = LegacyConfig( [('watch', config_options.ListOfPaths())], config_file_path=os.path.join(base_path, 'mkdocs.yml'), ) @@ -649,22 +684,19 @@ def test_paths_localized_to_config(self): self.assertEqual(cfg['watch'], [os.path.join(base_path, 'foo')]) -class SiteDirTest(unittest.TestCase): +class SiteDirTest(TestCase): def validate_config(self, config): """Given a config with values for site_dir and doc_dir, run site_dir post_validation.""" - site_dir = config_options.SiteDir() - docs_dir = config_options.Dir() - fname = os.path.join(os.path.abspath('..'), 'mkdocs.yml') + class Schema(Config): + site_dir = config_options.SiteDir() + docs_dir = config_options.Dir() - config['docs_dir'] = docs_dir.validate(config['docs_dir']) - config['site_dir'] = site_dir.validate(config['site_dir']) + fname = os.path.join(os.path.abspath('..'), 'mkdocs.yml') - schema = [ - ('site_dir', site_dir), - ('docs_dir', docs_dir), - ] - cfg = Config(schema, fname) + config['docs_dir'] = Schema.docs_dir.validate(config['docs_dir']) + config['site_dir'] = Schema.site_dir.validate(config['site_dir']) + cfg = Schema(fname) cfg.load_dict(config) failed, warned = cfg.validate() @@ -721,7 +753,7 @@ def test_common_prefix(self): assert self.validate_config(test_config) -class ThemeTest(unittest.TestCase): +class ThemeTest(TestCase): def test_theme_as_string(self): option = config_options.Theme() value = option.validate("mkdocs") @@ -831,18 +863,22 @@ def test_post_validation_locale_invalid_type(self): option.post_validation(config, 'theme') def test_post_validation_locale(self): - config = { - 'theme': { - 'name': 'mkdocs', - 'locale': 'fr', + class Schema(Config): + theme = config_options.Theme() + + conf = self.get_config( + Schema, + { + 'theme': { + 'name': 'mkdocs', + 'locale': 'fr', + }, }, - } - option = config_options.Theme() - option.post_validation(config, 'theme') - self.assertEqual('fr', config['theme']['locale'].language) + ) + self.assertEqual(conf.theme['locale'].language, 'fr') -class NavTest(unittest.TestCase): +class NavTest(TestCase): def test_old_format(self): option = config_options.Nav() with self.assertRaises(config_options.ValidationError) as cm: @@ -957,14 +993,14 @@ def test_warns_for_dict(self): ) -class PrivateTest(unittest.TestCase): +class PrivateTest(TestCase): def test_defined(self): option = config_options.Private() with self.assertRaises(config_options.ValidationError): option.validate('somevalue') -class SubConfigTest(unittest.TestCase): +class SubConfigTest(TestCase): def test_subconfig_wrong_type(self): # Test that an error is raised if subconfig does not receive a dict option = config_options.SubConfig() @@ -1025,74 +1061,76 @@ def test_subconfig_strict(self): class ConfigItemsTest(TestCase): - def test_non_required(self): - schema = { - 'sub': config_options.ConfigItems( - ('opt', config_options.Type(int)), - validate=True, - ), - }.items() + def test_optional(self): + class Sub(Config): + opt = config_options.Optional(config_options.Type(int)) + + class Schema(Config): + sub = config_options.Optional(config_options.ConfigItems(Sub)) - cfg = self.get_config(schema, {}) + cfg = self.get_config(Schema, {}) self.assertEqual(cfg['sub'], []) - cfg = self.get_config(schema, {'sub': None}) + cfg = self.get_config(Schema, {'sub': None}) self.assertEqual(cfg['sub'], []) - cfg = self.get_config(schema, {'sub': [{'opt': 1}, {}]}) + cfg = self.get_config(Schema, {'sub': [{'opt': 1}, {}]}) self.assertEqual(cfg['sub'], [{'opt': 1}, {'opt': None}]) + cfg = self.get_config(Schema, {'sub': None}) + self.assertEqual(cfg['sub'], []) + + cfg = self.get_config(Schema, {'sub': []}) + + cfg = self.get_config(Schema, {'sub': [{'opt': 1}, {'opt': 2}]}) + self.assertEqual(cfg['sub'], [{'opt': 1}, {'opt': 2}]) + def test_required(self): - schema = { - 'sub': config_options.ConfigItems( - ('opt', config_options.Type(str, required=True)), - validate=True, - ), - }.items() + class Sub(Config): + opt = config_options.Type(int) + + class Schema(Config): + sub = config_options.Optional(config_options.ConfigItems(Sub)) - cfg = self.get_config(schema, {}) + cfg = self.get_config(Schema, {}) self.assertEqual(cfg['sub'], []) - cfg = self.get_config(schema, {'sub': None}) + cfg = self.get_config(Schema, {'sub': None}) self.assertEqual(cfg['sub'], []) with self.expect_error( - sub="Sub-option 'opt' configuration error: Expected type: but received: " + sub="Sub-option 'opt' configuration error: Expected type: but received: " ): - cfg = self.get_config(schema, {'sub': [{'opt': 1}, {}]}) + cfg = self.get_config(Schema, {'sub': [{'opt': 'asdf'}, {}]}) - def test_common(self): - for required in False, True: - with self.subTest(required=required): - schema = { - 'sub': config_options.ConfigItems( - ('opt', config_options.Type(int, required=required)), - validate=True, - ), - }.items() + cfg = self.get_config(Schema, {'sub': None}) + self.assertEqual(cfg['sub'], []) - cfg = self.get_config(schema, {'sub': None}) - self.assertEqual(cfg['sub'], []) + cfg = self.get_config(Schema, {'sub': []}) - cfg = self.get_config(schema, {'sub': []}) + cfg = self.get_config(Schema, {'sub': [{'opt': 1}, {'opt': 2}]}) + self.assertEqual(cfg['sub'], [{'opt': 1}, {'opt': 2}]) - cfg = self.get_config(schema, {'sub': [{'opt': 1}, {'opt': 2}]}) - self.assertEqual(cfg['sub'], [{'opt': 1}, {'opt': 2}]) + with self.expect_error( + sub="Sub-option 'opt' configuration error: Expected type: but " + "received: " + ): + self.get_config(Schema, {'sub': [{'opt': 'z'}, {'opt': 2}]}) - with self.expect_error( - sub="Sub-option 'opt' configuration error: " - "Expected type: but received: " - ): - cfg = self.get_config(schema, {'sub': [{'opt': 'z'}, {'opt': 2}]}) + with self.expect_error( + sub="Sub-option 'opt' configuration error: " + "Expected type: but received: " + ): + cfg = self.get_config(Schema, {'sub': [{'opt': 'z'}, {'opt': 2}]}) - with self.expect_error( - sub="The configuration is invalid. The expected type was a key value mapping " - "(a python dict) but we got an object of type: " - ): - cfg = self.get_config(schema, {'sub': [1, 2]}) + with self.expect_error( + sub="The configuration is invalid. The expected type was a key value mapping " + "(a python dict) but we got an object of type: " + ): + cfg = self.get_config(Schema, {'sub': [1, 2]}) -class MarkdownExtensionsTest(unittest.TestCase): +class MarkdownExtensionsTest(TestCase): @patch('markdown.Markdown') def test_simple_list(self, mockMd): option = config_options.MarkdownExtensions() diff --git a/requirements/lint.txt b/requirements/lint.txt index 0d5682af39..ec61379de2 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -2,6 +2,7 @@ black isort flake8 mypy +typing-extensions types-setuptools types-PyYAML types-Markdown diff --git a/tox.ini b/tox.ini index 4d7e3b1d59..033fa5951b 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,7 @@ commands={envbindir}/flake8 mkdocs [testenv:mypy] deps= mypy + typing-extensions types-setuptools types-PyYAML types-Markdown