From 19fd8f526e13719150cb8975b4ab4b71c333c945 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Mon, 12 Sep 2022 23:17:14 +0200 Subject: [PATCH] =?UTF-8?q?=E2=80=A0=20Tests=20for=20class-based=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [†]: do not commit as-is, this should become a new file instead, and the old file kept --- mkdocs/tests/config/config_options_tests.py | 408 ++++++++++---------- requirements/lint.txt | 1 + tox.ini | 1 + 3 files changed, 211 insertions(+), 199 deletions(-) diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py index a1b0b3c1eb..3c4d67e409 100644 --- a/mkdocs/tests/config/config_options_tests.py +++ b/mkdocs/tests/config/config_options_tests.py @@ -4,13 +4,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 base, config_options +from mkdocs.config import config_options +from mkdocs.config.base import Config from mkdocs.tests.base import tempdir from mkdocs.utils import yaml_load +SomeConfig = TypeVar('SomeConfig', bound=Config) + class UnexpectedError(Exception): pass @@ -27,8 +39,14 @@ def expect_error(self, **kwargs): else: self.assertEqual(f'{key}="{msg}"', str(cm.exception)) - def get_config(self, schema, cfg, warnings={}, config_file_path=None): - config = base.Config(base.get_schema(schema), config_file_path=config_file_path) + def get_config( + self, + config_class: Type[SomeConfig], + cfg: Dict[str, Any], + warnings={}, + config_file_path=None, + ) -> SomeConfig: + config = config_class(config_file_path=config_file_path) config.load_dict(cfg) actual_errors, actual_warnings = config.validate() if actual_errors: @@ -37,64 +55,24 @@ def get_config(self, schema, cfg, warnings={}, config_file_path=None): return config -class OptionallyRequiredTest(TestCase): - def test_empty(self): - class Schema: - option = config_options.OptionallyRequired() - - conf = self.get_config(Schema, {'option': None}) - self.assertEqual(conf['option'], None) - - self.assertEqual(Schema.option.required, False) - - def test_required(self): - class Schema: - option = config_options.OptionallyRequired(required=True) - - with self.expect_error(option="Required configuration not provided."): - self.get_config(Schema, {'option': None}) - - self.assertEqual(Schema.option.required, True) - - def test_required_no_default(self): - class Schema: - option = config_options.OptionallyRequired(required=True) - - conf = self.get_config(Schema, {'option': 2}) - self.assertEqual(conf['option'], 2) - - def test_default(self): - class Schema: - option = config_options.OptionallyRequired(default=1) - - conf = self.get_config(Schema, {'option': None}) - self.assertEqual(conf['option'], 1) - - def test_replace_default(self): - class Schema: - option = config_options.OptionallyRequired(default=1) - - conf = self.get_config(Schema, {'option': 2}) - self.assertEqual(conf['option'], 2) - - class TypeTest(TestCase): - def test_single_type(self): - class Schema: + def test_single_type(self) -> None: + class Schema(Config): option = config_options.Type(str) conf = self.get_config(Schema, {'option': "Testing"}) - self.assertEqual(conf['option'], "Testing") + assert_type(conf.option, str) + self.assertEqual(conf.option, "Testing") - def test_multiple_types(self): - class Schema: + def test_multiple_types(self) -> None: + class Schema(Config): option = config_options.Type((list, tuple)) conf = self.get_config(Schema, {'option': [1, 2, 3]}) - self.assertEqual(conf['option'], [1, 2, 3]) + self.assertEqual(conf.option, [1, 2, 3]) conf = self.get_config(Schema, {'option': (1, 2, 3)}) - self.assertEqual(conf['option'], (1, 2, 3)) + self.assertEqual(conf.option, (1, 2, 3)) with self.expect_error( option="Expected type: (, ) but received: " @@ -102,7 +80,7 @@ class Schema: self.get_config(Schema, {'option': {'a': 1}}) def test_length(self): - class Schema: + class Schema(Config): option = config_options.Type(str, length=7) conf = self.get_config(Schema, {'option': "Testing"}) @@ -115,32 +93,54 @@ class Schema: class ChoiceTest(TestCase): - def test_valid_choice(self): - class Schema: + def test_valid_choice(self) -> None: + class Schema(Config): option = config_options.Choice(('python', 'node')) conf = self.get_config(Schema, {'option': 'python'}) - self.assertEqual(conf['option'], 'python') + assert_type(conf.option, str) + self.assertEqual(conf.option, 'python') - def test_default(self): - class Schema: - option = config_options.Choice(('python', 'node'), default='node') + def test_optional(self) -> None: + class Schema(Config): + option = config_options.Optional(config_options.Choice(('python', 'node'))) - conf = self.get_config(Schema, {'option': None}) - self.assertEqual(conf['option'], 'node') + conf = self.get_config(Schema, {'option': 'python'}) + assert_type(conf.option, Optional[str]) + self.assertEqual(conf.option, 'python') - def test_excluded_default(self): + 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') + + conf = self.get_config(Schema, {}) + assert_type(conf.option, str) + self.assertEqual(conf.option, 'b') + + 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_invalid_default(self): with self.assertRaises(ValueError): - config_options.Choice(('python', 'node'), default='a') + config_options.Choice(('a', 'b'), default='c') + with self.assertRaises(ValueError): + config_options.Choice(('a', 'b'), default='c', required=True) - def test_invalid_choice(self): - class Schema: + 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) @@ -148,7 +148,7 @@ def test_invalid_choices(self): class DeprecatedTest(TestCase): def test_deprecated_option_simple(self): - class Schema: + class Schema(Config): d = config_options.Deprecated() self.get_config( @@ -161,13 +161,13 @@ class Schema: ) def test_deprecated_option_message(self): - class Schema: + class Schema(Config): d = config_options.Deprecated(message='custom message for {} key') self.get_config(Schema, {'d': 'value'}, warnings={'d': 'custom message for d key'}) def test_deprecated_option_with_type(self): - class Schema: + class Schema(Config): d = config_options.Deprecated(option_type=config_options.Type(str)) self.get_config( @@ -180,7 +180,7 @@ class Schema: ) def test_deprecated_option_with_invalid_type(self): - class Schema: + class Schema(Config): d = config_options.Deprecated(option_type=config_options.Type(list)) with self.expect_error(d="Expected type: but received: "): @@ -194,7 +194,7 @@ class Schema: ) def test_removed_option(self): - class Schema: + class Schema(Config): d = config_options.Deprecated(removed=True, moved_to='foo') with self.expect_error( @@ -203,13 +203,13 @@ class Schema: self.get_config(Schema, {'d': 'value'}) def test_deprecated_option_with_type_undefined(self): - class Schema: + class Schema(Config): option = config_options.Deprecated(option_type=config_options.Type(str)) self.get_config(Schema, {'option': None}) def test_deprecated_option_move(self): - class Schema: + class Schema(Config): new = config_options.Type(str) old = config_options.Deprecated(moved_to='new') @@ -224,7 +224,7 @@ class Schema: self.assertEqual(conf, {'new': 'value', 'old': None}) def test_deprecated_option_move_complex(self): - class Schema: + class Schema(Config): foo = config_options.Type(dict) old = config_options.Deprecated(moved_to='foo.bar') @@ -239,7 +239,7 @@ class Schema: self.assertEqual(conf, {'foo': {'bar': 'value'}, 'old': None}) def test_deprecated_option_move_existing(self): - class Schema: + class Schema(Config): foo = config_options.Type(dict) old = config_options.Deprecated(moved_to='foo.bar') @@ -254,7 +254,7 @@ class Schema: self.assertEqual(conf, {'foo': {'existing': 'existing', 'bar': 'value'}, 'old': None}) def test_deprecated_option_move_invalid(self): - class Schema: + class Schema(Config): foo = config_options.Type(dict) old = config_options.Deprecated(moved_to='foo.bar') @@ -270,24 +270,28 @@ class Schema: class IpAddressTest(TestCase): - class Schema: + class Schema(Config): option = config_options.IpAddress() - def test_valid_address(self): + def test_valid_address(self) -> None: addr = '127.0.0.1:8000' conf = self.get_config(self.Schema, {'option': addr}) - self.assertEqual(str(conf['option']), addr) - self.assertEqual(conf['option'].host, '127.0.0.1') - self.assertEqual(conf['option'].port, 8000) + + assert_type(conf.option, config_options._IpAddressValue) + assert_type(conf.option.host, str) + assert_type(conf.option.port, int) + self.assertEqual(str(conf.option), addr) + self.assertEqual(conf.option.host, '127.0.0.1') + self.assertEqual(conf.option.port, 8000) def test_valid_IPv6_address(self): addr = '::1:8000' conf = self.get_config(self.Schema, {'option': addr}) - self.assertEqual(str(conf['option']), addr) - self.assertEqual(conf['option'].host, '::1') - self.assertEqual(conf['option'].port, 8000) + self.assertEqual(str(conf.option), addr) + self.assertEqual(conf.option.host, '::1') + self.assertEqual(conf.option.port, 8000) def test_valid_full_IPv6_address(self): addr = '[2001:db8:85a3::8a2e:370:7334]:123' @@ -307,7 +311,7 @@ def test_named_address(self): def test_default_address(self): addr = '127.0.0.1:8000' - class Schema: + class Schema(Config): option = config_options.IpAddress(default=addr) conf = self.get_config(Schema, {'option': None}) @@ -346,7 +350,7 @@ def test_invalid_address_missing_port(self): self.get_config(self.Schema, {'option': '127.0.0.1'}) def test_unsupported_address(self): - class Schema: + class Schema(Config): dev_addr = config_options.IpAddress() self.get_config( @@ -362,7 +366,7 @@ class Schema: ) def test_unsupported_IPv6_address(self): - class Schema: + class Schema(Config): dev_addr = config_options.IpAddress() self.get_config( @@ -379,7 +383,7 @@ class Schema: class URLTest(TestCase): def test_valid_url(self): - class Schema: + class Schema(Config): option = config_options.URL() conf = self.get_config(Schema, {'option': "https://mkdocs.org"}) @@ -389,7 +393,7 @@ class Schema: self.assertEqual(conf['option'], "") def test_valid_url_is_dir(self): - class Schema: + class Schema(Config): option = config_options.URL(is_dir=True) conf = self.get_config(Schema, {'option': "http://mkdocs.org/"}) @@ -399,7 +403,7 @@ class Schema: self.assertEqual(conf['option'], "https://mkdocs.org/") def test_invalid_url(self): - class Schema: + class Schema(Config): option = config_options.URL() for url in "www.mkdocs.org", "//mkdocs.org/test", "http:/mkdocs.org/", "/hello/": @@ -410,7 +414,7 @@ class Schema: self.get_config(Schema, {'option': url}) def test_invalid_type(self): - class Schema: + class Schema(Config): option = config_options.URL() with self.expect_error(option="Unable to parse the URL."): @@ -418,11 +422,11 @@ class Schema: class EditURITest(TestCase): - class Schema: - repo_url = config_options.URL() - repo_name = config_options.RepoName('repo_url') - edit_uri_template = config_options.EditURITemplate('edit_uri') - edit_uri = config_options.EditURI('repo_url') + class Schema(Config): + repo_url = config_options.Optional(config_options.URL()) + repo_name = config_options.Optional(config_options.RepoName('repo_url')) + edit_uri_template = config_options.Optional(config_options.EditURITemplate('edit_uri')) + edit_uri = config_options.Optional(config_options.EditURI('repo_url')) def test_repo_name_github(self): config = self.get_config( @@ -547,7 +551,7 @@ def test_edit_uri_template_warning(self): class ListOfItemsTest(TestCase): def test_int_type(self): - class Schema: + class Schema(Config): option = config_options.ListOfItems(config_options.Type(int)) cfg = self.get_config(Schema, {'option': [1, 2, 3]}) @@ -559,7 +563,7 @@ class Schema: cfg = self.get_config(Schema, {'option': [1, None, 3]}) def test_combined_float_type(self): - class Schema: + class Schema(Config): option = config_options.ListOfItems(config_options.Type((int, float))) cfg = self.get_config(Schema, {'option': [1.4, 2, 3]}) @@ -571,8 +575,8 @@ class Schema: self.get_config(Schema, {'option': ['a']}) def test_list_default(self): - class Schema: - option = config_options.ListOfItems(config_options.Type(int)) + class Schema(Config): + option = config_options.Optional(config_options.ListOfItems(config_options.Type(int))) cfg = self.get_config(Schema, {}) self.assertEqual(cfg['option'], []) @@ -581,8 +585,10 @@ class Schema: self.assertEqual(cfg['option'], []) def test_none_default(self): - class Schema: - option = config_options.ListOfItems(config_options.Type(str), default=None) + 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) @@ -594,14 +600,14 @@ class Schema: self.assertEqual(cfg['option'], ['foo']) def test_string_not_a_list_of_strings(self): - class Schema: + 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'}) def test_post_validation_error(self): - class Schema: + class Schema(Config): option = config_options.ListOfItems(config_options.IpAddress()) with self.expect_error(option="'asdf' is not a valid port"): @@ -614,7 +620,7 @@ def test_valid_dir(self): with self.subTest(cls): d = os.path.dirname(__file__) - class Schema: + class Schema(Config): option = cls(exists=True) conf = self.get_config(Schema, {'option': d}) @@ -625,7 +631,7 @@ def test_valid_file(self): with self.subTest(cls): f = __file__ - class Schema: + class Schema(Config): option = cls(exists=True) conf = self.get_config(Schema, {'option': f}) @@ -636,7 +642,7 @@ def test_missing_without_exists(self): with self.subTest(cls): d = os.path.join("not", "a", "real", "path", "I", "hope") - class Schema: + class Schema(Config): option = cls() conf = self.get_config(Schema, {'option': d}) @@ -647,7 +653,7 @@ def test_missing_but_required(self): with self.subTest(cls): d = os.path.join("not", "a", "real", "path", "I", "hope") - class Schema: + class Schema(Config): option = cls(exists=True) with self.expect_error(option=re.compile(r"The path '.+' isn't an existing .+")): @@ -656,7 +662,7 @@ class Schema: def test_not_a_dir(self): d = __file__ - class Schema: + class Schema(Config): option = config_options.Dir(exists=True) with self.expect_error(option=re.compile(r"The path '.+' isn't an existing directory.")): @@ -665,7 +671,7 @@ class Schema: def test_not_a_file(self): d = os.path.dirname(__file__) - class Schema: + class Schema(Config): option = config_options.File(exists=True) with self.expect_error(option=re.compile(r"The path '.+' isn't an existing file.")): @@ -675,7 +681,7 @@ def test_incorrect_type_error(self): for cls in config_options.Dir, config_options.File, config_options.FilesystemObject: with self.subTest(cls): - class Schema: + class Schema(Config): option = cls() with self.expect_error( @@ -691,14 +697,14 @@ def test_with_unicode(self): for cls in config_options.Dir, config_options.File, config_options.FilesystemObject: with self.subTest(cls): - class Schema: + class Schema(Config): dir = cls() conf = self.get_config(Schema, {'dir': 'юникод'}) self.assertIsInstance(conf['dir'], str) def test_dir_bytes(self): - class Schema: + class Schema(Config): dir = config_options.Dir() with self.expect_error(dir="Expected type: but received: "): @@ -709,7 +715,7 @@ def test_config_dir_prepended(self): with self.subTest(cls): base_path = os.path.dirname(os.path.abspath(__file__)) - class Schema: + class Schema(Config): dir = cls() conf = self.get_config( @@ -720,7 +726,7 @@ class Schema: self.assertEqual(conf['dir'], os.path.join(base_path, 'foo')) def test_site_dir_is_config_dir_fails(self): - class Schema: + class Schema(Config): dir = config_options.DocsDir() with self.expect_error( @@ -738,7 +744,7 @@ class ListOfPathsTest(TestCase): def test_valid_path(self): paths = [os.path.dirname(__file__)] - class Schema: + class Schema(Config): option = config_options.ListOfPaths() self.get_config(Schema, {'option': paths}) @@ -746,7 +752,7 @@ class Schema: def test_missing_path(self): paths = [os.path.join("does", "not", "exist", "i", "hope")] - class Schema: + class Schema(Config): option = config_options.ListOfPaths() with self.expect_error( @@ -757,7 +763,7 @@ class Schema: def test_non_path(self): paths = [os.path.dirname(__file__), None] - class Schema: + class Schema(Config): option = config_options.ListOfPaths() with self.expect_error( @@ -768,7 +774,7 @@ class Schema: def test_empty_list(self): paths = [] - class Schema: + class Schema(Config): option = config_options.ListOfPaths() self.get_config(Schema, {'option': paths}) @@ -776,7 +782,7 @@ class Schema: def test_non_list(self): paths = os.path.dirname(__file__) - class Schema: + class Schema(Config): option = config_options.ListOfPaths() with self.expect_error(option="Expected a list of items, but a was given."): @@ -785,7 +791,7 @@ class Schema: def test_file(self): paths = [__file__] - class Schema: + class Schema(Config): option = config_options.ListOfPaths() self.get_config(Schema, {'option': paths}) @@ -795,7 +801,7 @@ def test_paths_localized_to_config(self, base_path): with open(os.path.join(base_path, 'foo'), 'w') as f: f.write('hi') - class Schema: + class Schema(Config): watch = config_options.ListOfPaths() conf = self.get_config( @@ -808,7 +814,7 @@ class Schema: class SiteDirTest(TestCase): - class Schema: + class Schema(Config): site_dir = config_options.SiteDir() docs_dir = config_options.Dir() @@ -866,14 +872,14 @@ def test_common_prefix(self): class ThemeTest(TestCase): def test_theme_as_string(self): - class Schema: + class Schema(Config): option = config_options.Theme() conf = self.get_config(Schema, {'option': "mkdocs"}) self.assertEqual(conf['option'].name, 'mkdocs') def test_uninstalled_theme_as_string(self): - class Schema: + class Schema(Config): option = config_options.Theme() with self.expect_error( @@ -884,7 +890,7 @@ class Schema: self.get_config(Schema, {'option': "mkdocs2"}) def test_theme_default(self): - class Schema: + class Schema(Config): option = config_options.Theme(default='mkdocs') conf = self.get_config(Schema, {'option': None}) @@ -895,7 +901,7 @@ def test_theme_as_simple_config(self): 'name': 'mkdocs', } - class Schema: + class Schema(Config): option = config_options.Theme() conf = self.get_config(Schema, {'option': config}) @@ -910,7 +916,7 @@ def test_theme_as_complex_config(self, custom_dir): 'show_sidebar': False, } - class Schema: + class Schema(Config): option = config_options.Theme() conf = self.get_config(Schema, {'option': config}) @@ -927,7 +933,7 @@ def test_theme_name_is_none(self): 'name': None, } - class Schema: + class Schema(Config): option = config_options.Theme() with self.expect_error( @@ -940,7 +946,7 @@ def test_theme_config_missing_name(self): 'custom_dir': 'custom', } - class Schema: + class Schema(Config): option = config_options.Theme() with self.expect_error(option="No theme name set."): @@ -951,7 +957,7 @@ def test_uninstalled_theme_as_config(self): 'name': 'mkdocs2', } - class Schema: + class Schema(Config): option = config_options.Theme() with self.expect_error( @@ -964,7 +970,7 @@ class Schema: def test_theme_invalid_type(self): config = ['mkdocs2'] - class Schema: + class Schema(Config): option = config_options.Theme() with self.expect_error( @@ -979,7 +985,7 @@ def test_post_validation_none_theme_name_and_missing_custom_dir(self): }, } - class Schema: + class Schema(Config): theme = config_options.Theme() with self.expect_error( @@ -997,7 +1003,7 @@ def test_post_validation_inexisting_custom_dir(self, abs_base_path): }, } - class Schema: + class Schema(Config): theme = config_options.Theme() with self.expect_error( @@ -1013,7 +1019,7 @@ def test_post_validation_locale_none(self): }, } - class Schema: + class Schema(Config): theme = config_options.Theme() with self.expect_error(theme="'theme.locale' must be a string."): @@ -1027,7 +1033,7 @@ def test_post_validation_locale_invalid_type(self): }, } - class Schema: + class Schema(Config): theme = config_options.Theme() with self.expect_error(theme="'theme.locale' must be a string."): @@ -1041,7 +1047,7 @@ def test_post_validation_locale(self): }, } - class Schema: + class Schema(Config): theme = config_options.Theme() conf = self.get_config(Schema, config) @@ -1049,7 +1055,7 @@ class Schema: class NavTest(TestCase): - class Schema: + class Schema(Config): option = config_options.Nav() def test_old_format(self): @@ -1135,7 +1141,7 @@ def test_warns_for_dict(self): class PrivateTest(TestCase): def test_defined(self): - class Schema: + class Schema(Config): option = config_options.Private() with self.expect_error(option="For internal use only."): @@ -1145,7 +1151,7 @@ class Schema: class SubConfigTest(TestCase): def test_subconfig_wrong_type(self): # Test that an error is raised if subconfig does not receive a dict - class Schema: + class Schema(Config): option = config_options.SubConfig() for val in "not_a_dict", ("not_a_dict",), ["not_a_dict"]: @@ -1162,28 +1168,28 @@ def test_subconfig_default(self): """Default behaviour of subconfig: validation is ignored""" # Nominal - class Schema: + class Schema(Config): option = config_options.SubConfig(('c', config_options.Choice(('foo', 'bar')))) conf = self.get_config(Schema, {'option': {'c': 'foo'}}) self.assertEqual(conf, {'option': {'c': 'foo'}}) # Invalid option: No error - class Schema: + class Schema(Config): option = config_options.SubConfig(('c', config_options.Choice(('foo', 'bar')))) conf = self.get_config(Schema, {'option': {'c': True}}) self.assertEqual(conf, {'option': {'c': True}}) # Missing option: Will be considered optional with default None - class Schema: + class Schema(Config): option = config_options.SubConfig(('c', config_options.Choice(('foo', 'bar')))) conf = self.get_config(Schema, {'option': {}}) self.assertEqual(conf, {'option': {'c': None}}) # Unknown option: No warning - class Schema: + class Schema(Config): option = config_options.SubConfig(('c', config_options.Choice(('foo', 'bar')))) conf = self.get_config(Schema, {'option': {'unknown_key_is_ok': 0}}) @@ -1193,7 +1199,7 @@ def test_subconfig_strict(self): """Strict validation mode for subconfigs.""" # Unknown option: warning - class Schema: + class Schema(Config): option = config_options.SubConfig(validate=True) conf = self.get_config( @@ -1204,7 +1210,7 @@ class Schema: self.assertEqual(conf, {'option': {"unknown": 0}}) # Invalid option: error - class Schema: + class Schema(Config): option = config_options.SubConfig( ('c', config_options.Choice(('foo', 'bar'))), validate=True, @@ -1221,7 +1227,7 @@ class Schema: def test_subconfig_with_multiple_items(self): # This had a bug where subsequent items would get merged into the same dict. - class Schema: + class Schema(Config): items = mkdocs.config.config_options.ConfigItems( ("value", mkdocs.config.config_options.Type(str)), ) @@ -1239,12 +1245,12 @@ class Schema: class ConfigItemsTest(TestCase): - def test_non_required(self): - class Schema: - sub = config_options.ConfigItems( - ('opt', config_options.Type(int)), - validate=True, - ) + 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, {}) self.assertEqual(cfg['sub'], []) @@ -1255,12 +1261,20 @@ class Schema: 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): - class Schema: - sub = config_options.ConfigItems( - ('opt', config_options.Type(str, required=True)), - validate=True, - ) + class Sub(Config): + opt = config_options.Type(int) + + class Schema(Config): + sub = config_options.Optional(config_options.ConfigItems(Sub)) cfg = self.get_config(Schema, {}) self.assertEqual(cfg['sub'], []) @@ -1269,45 +1283,41 @@ class Schema: 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): - - class Schema: - sub = config_options.ConfigItems( - ('opt', config_options.Type(int, required=required)), - validate=True, - ) + 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(TestCase): @patch('markdown.Markdown') def test_simple_list(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1319,7 +1329,7 @@ class Schema: @patch('markdown.Markdown') def test_list_dicts(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1341,7 +1351,7 @@ class Schema: @patch('markdown.Markdown') def test_mixed_list(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1361,7 +1371,7 @@ class Schema: @patch('markdown.Markdown') def test_dict_of_dicts(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1383,7 +1393,7 @@ class Schema: @patch('markdown.Markdown') def test_builtins(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions(builtins=['meta', 'toc']) config = { @@ -1394,7 +1404,7 @@ class Schema: self.assertEqual(conf['mdx_configs'], {}) def test_duplicates(self): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions(builtins=['meta', 'toc']) config = { @@ -1405,7 +1415,7 @@ class Schema: self.assertEqual(conf['mdx_configs'], {}) def test_builtins_config(self): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions(builtins=['meta', 'toc']) config = { @@ -1419,7 +1429,7 @@ class Schema: @patch('markdown.Markdown') def test_configkey(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions(configkey='bar') config = { @@ -1437,7 +1447,7 @@ class Schema: ) def test_missing_default(self): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = {} @@ -1446,7 +1456,7 @@ class Schema: self.assertEqual(conf['mdx_configs'], {}) def test_none(self): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions(default=[]) config = { @@ -1458,7 +1468,7 @@ class Schema: @patch('markdown.Markdown') def test_not_list(self, mockMd): - class Schema: + class Schema(Config): option = config_options.MarkdownExtensions() with self.expect_error(option="Invalid Markdown Extensions configuration"): @@ -1466,7 +1476,7 @@ class Schema: @patch('markdown.Markdown') def test_invalid_config_option(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1481,7 +1491,7 @@ class Schema: @patch('markdown.Markdown') def test_invalid_config_item(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1494,7 +1504,7 @@ class Schema: @patch('markdown.Markdown') def test_invalid_dict_item(self, mockMd): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1506,7 +1516,7 @@ class Schema: self.get_config(Schema, config) def test_unknown_extension(self): - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() config = { @@ -1520,7 +1530,7 @@ class Schema: def test_multiple_markdown_config_instances(self): # This had a bug where an extension config would persist to separate # config instances that didn't specify extensions. - class Schema: + class Schema(Config): markdown_extensions = config_options.MarkdownExtensions() conf = self.get_config( diff --git a/requirements/lint.txt b/requirements/lint.txt index 31c29a4b74..c9704506c1 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 5a82e7921a..bac7c3fbc8 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