Skip to content

Commit fd40166

Browse files
committed
Move file validations out of the config module
This validations will be executed in build time. This will unblock users with dependencies in submodules. Closes #5238
1 parent bbae0dc commit fd40166

File tree

4 files changed

+13
-176
lines changed

4 files changed

+13
-176
lines changed

readthedocs/config/config.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
validate_bool,
3131
validate_choice,
3232
validate_dict,
33-
validate_directory,
34-
validate_file,
33+
validate_path,
3534
validate_list,
3635
validate_string,
3736
)
@@ -506,7 +505,7 @@ def validate_conda(self):
506505
with self.catch_validation_error('conda.file'):
507506
if 'file' not in raw_conda:
508507
raise ValidationError('file', VALUE_NOT_FOUND)
509-
conda_environment = validate_file(
508+
conda_environment = validate_path(
510509
raw_conda['file'],
511510
self.base_path,
512511
)
@@ -523,7 +522,7 @@ def validate_requirements_file(self):
523522
if not requirements_file:
524523
return None
525524
with self.catch_validation_error('requirements_file'):
526-
requirements_file = validate_file(
525+
requirements_file = validate_path(
527526
requirements_file,
528527
self.base_path,
529528
)
@@ -692,7 +691,7 @@ def validate_conda(self):
692691
conda = {}
693692
with self.catch_validation_error('conda.environment'):
694693
environment = self.pop_config('conda.environment', raise_ex=True)
695-
conda['environment'] = validate_file(environment, self.base_path)
694+
conda['environment'] = validate_path(environment, self.base_path)
696695
return conda
697696

698697
def validate_build(self):
@@ -798,15 +797,15 @@ def validate_python_install(self, index):
798797
if 'requirements' in raw_install:
799798
requirements_key = key + '.requirements'
800799
with self.catch_validation_error(requirements_key):
801-
requirements = validate_file(
800+
requirements = validate_path(
802801
self.pop_config(requirements_key),
803802
self.base_path,
804803
)
805804
python_install['requirements'] = requirements
806805
elif 'path' in raw_install:
807806
path_key = key + '.path'
808807
with self.catch_validation_error(path_key):
809-
path = validate_directory(
808+
path = validate_path(
810809
self.pop_config(path_key),
811810
self.base_path,
812811
)
@@ -883,7 +882,7 @@ def validate_mkdocs(self):
883882
with self.catch_validation_error('mkdocs.configuration'):
884883
configuration = self.pop_config('mkdocs.configuration', None)
885884
if configuration is not None:
886-
configuration = validate_file(configuration, self.base_path)
885+
configuration = validate_path(configuration, self.base_path)
887886
mkdocs['configuration'] = configuration
888887

889888
with self.catch_validation_error('mkdocs.fail_on_warning'):
@@ -930,7 +929,7 @@ def validate_sphinx(self):
930929
configuration,
931930
)
932931
if configuration is not None:
933-
configuration = validate_file(configuration, self.base_path)
932+
configuration = validate_path(configuration, self.base_path)
934933
sphinx['configuration'] = configuration
935934

936935
with self.catch_validation_error('sphinx.fail_on_warning'):

readthedocs/config/tests/test_config.py

+1-94
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
import os
32
import re
43
import textwrap
@@ -852,16 +851,6 @@ def test_conda_check_valid(self, tmpdir):
852851
build.validate()
853852
assert build.conda.environment == str(tmpdir.join('environment.yml'))
854853

855-
def test_conda_check_invalid(self, tmpdir):
856-
apply_fs(tmpdir, {'environment.yml': ''})
857-
build = self.get_build_config(
858-
{'conda': {'environment': 'no_existing_environment.yml'}},
859-
source_file=str(tmpdir.join('readthedocs.yml')),
860-
)
861-
with raises(InvalidConfig) as excinfo:
862-
build.validate()
863-
assert excinfo.value.key == 'conda.environment'
864-
865854
@pytest.mark.parametrize('value', [3, [], 'invalid'])
866855
def test_conda_check_invalid_value(self, value):
867856
build = self.get_build_config({'conda': value})
@@ -1049,22 +1038,6 @@ def test_python_install_check_default(self, tmpdir):
10491038
assert install[0].method == PIP
10501039
assert install[0].extra_requirements == []
10511040

1052-
def test_python_install_path_check_invalid(self, tmpdir):
1053-
build = self.get_build_config(
1054-
{
1055-
'python': {
1056-
'install': [{
1057-
'path': 'noexists',
1058-
'method': 'pip',
1059-
}],
1060-
},
1061-
},
1062-
source_file=str(tmpdir.join('readthedocs.yml')),
1063-
)
1064-
with raises(InvalidConfig) as excinfo:
1065-
build.validate()
1066-
assert excinfo.value.key == 'python.install.0.path'
1067-
10681041
@pytest.mark.parametrize('value', ['invalid', 'apt'])
10691042
def test_python_install_method_check_invalid(self, value, tmpdir):
10701043
build = self.get_build_config(
@@ -1100,26 +1073,6 @@ def test_python_install_requirements_check_valid(self, tmpdir):
11001073
assert isinstance(install[0], PythonInstallRequirements)
11011074
assert install[0].requirements == str(tmpdir.join('requirements.txt'))
11021075

1103-
def test_python_install_requirements_check_invalid(self, tmpdir):
1104-
apply_fs(tmpdir, {'requirements.txt': ''})
1105-
requirements_file = 'invalid'
1106-
build = self.get_build_config(
1107-
{
1108-
'python': {
1109-
'install': [{
1110-
'path': '.',
1111-
'requirements': requirements_file,
1112-
}],
1113-
},
1114-
},
1115-
source_file=str(tmpdir.join('readthedocs.yml')),
1116-
)
1117-
with raises(InvalidConfig) as excinfo:
1118-
build.validate()
1119-
assert excinfo.value.key == 'python.install.0.requirements'
1120-
error_msg = 'path {} does not exist'.format(requirements_file)
1121-
assert error_msg in str(excinfo.value)
1122-
11231076
def test_python_install_requirements_does_not_allow_null(self, tmpdir):
11241077
build = self.get_build_config(
11251078
{
@@ -1370,7 +1323,7 @@ def test_python_install_extra_requirements_allow_empty(self, tmpdir):
13701323
{
13711324
'python': {
13721325
'install': [{
1373-
'path': '',
1326+
'path': '.',
13741327
'method': 'pip',
13751328
'extra_requirements': [],
13761329
}],
@@ -1419,32 +1372,6 @@ def test_python_install_several_respects_order(self, tmpdir):
14191372

14201373
assert install[2].requirements == str(tmpdir.join('three.txt'))
14211374

1422-
def test_python_install_reports_correct_invalid_index(self, tmpdir):
1423-
apply_fs(tmpdir, {
1424-
'one': {},
1425-
'two': {},
1426-
})
1427-
build = self.get_build_config(
1428-
{
1429-
'python': {
1430-
'install': [{
1431-
'path': 'one',
1432-
'method': 'pip',
1433-
'extra_requirements': [],
1434-
}, {
1435-
'path': 'two',
1436-
'method': 'setuptools',
1437-
}, {
1438-
'requirements': 'three.txt',
1439-
}],
1440-
},
1441-
},
1442-
source_file=str(tmpdir.join('readthedocs.yml')),
1443-
)
1444-
with raises(InvalidConfig) as excinfo:
1445-
build.validate()
1446-
assert excinfo.value.key == 'python.install.2.requirements'
1447-
14481375
@pytest.mark.parametrize('value', [True, False])
14491376
def test_python_system_packages_check_valid(self, value):
14501377
build = self.get_build_config({
@@ -1557,16 +1484,6 @@ def test_sphinx_configuration_check_valid(self, tmpdir):
15571484
build.validate()
15581485
assert build.sphinx.configuration == str(tmpdir.join('conf.py'))
15591486

1560-
def test_sphinx_configuration_check_invalid(self, tmpdir):
1561-
apply_fs(tmpdir, {'conf.py': ''})
1562-
build = self.get_build_config(
1563-
{'sphinx': {'configuration': 'invalid.py'}},
1564-
source_file=str(tmpdir.join('readthedocs.yml')),
1565-
)
1566-
with raises(InvalidConfig) as excinfo:
1567-
build.validate()
1568-
assert excinfo.value.key == 'sphinx.configuration'
1569-
15701487
def test_sphinx_cant_be_used_with_mkdocs(self, tmpdir):
15711488
apply_fs(tmpdir, {'conf.py': ''})
15721489
build = self.get_build_config(
@@ -1669,16 +1586,6 @@ def test_mkdocs_configuration_check_valid(self, tmpdir):
16691586
assert build.doctype == 'mkdocs'
16701587
assert build.sphinx is None
16711588

1672-
def test_mkdocs_configuration_check_invalid(self, tmpdir):
1673-
apply_fs(tmpdir, {'mkdocs.yml': ''})
1674-
build = self.get_build_config(
1675-
{'mkdocs': {'configuration': 'invalid.yml'}},
1676-
source_file=str(tmpdir.join('readthedocs.yml')),
1677-
)
1678-
with raises(InvalidConfig) as excinfo:
1679-
build.validate()
1680-
assert excinfo.value.key == 'mkdocs.configuration'
1681-
16821589
def test_mkdocs_configuration_allow_null(self):
16831590
build = self.get_build_config(
16841591
{'mkdocs': {'configuration': None}},

readthedocs/config/tests/test_validation.py

-48
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
1-
# -*- coding: utf-8 -*-
21
import os
32

4-
from mock import patch
53
from pytest import raises
64

75
from readthedocs.config.validation import (
86
INVALID_BOOL,
97
INVALID_CHOICE,
10-
INVALID_DIRECTORY,
11-
INVALID_FILE,
128
INVALID_LIST,
13-
INVALID_PATH,
149
INVALID_STRING,
1510
ValidationError,
1611
validate_bool,
1712
validate_choice,
18-
validate_directory,
19-
validate_file,
2013
validate_list,
2114
validate_path,
2215
validate_string,
@@ -83,42 +76,6 @@ def test_it_rejects_string_types(self):
8376
assert excinfo.value.code == INVALID_LIST
8477

8578

86-
class TestValidateDirectory:
87-
88-
def test_it_uses_validate_path(self, tmpdir):
89-
patcher = patch('readthedocs.config.validation.validate_path')
90-
with patcher as validate_path:
91-
path = str(tmpdir.mkdir('a directory'))
92-
validate_path.return_value = path
93-
validate_directory(path, str(tmpdir))
94-
validate_path.assert_called_with(path, str(tmpdir))
95-
96-
def test_it_rejects_files(self, tmpdir):
97-
tmpdir.join('file').write('content')
98-
with raises(ValidationError) as excinfo:
99-
validate_directory('file', str(tmpdir))
100-
assert excinfo.value.code == INVALID_DIRECTORY
101-
102-
103-
class TestValidateFile:
104-
105-
def test_it_uses_validate_path(self, tmpdir):
106-
patcher = patch('readthedocs.config.validation.validate_path')
107-
with patcher as validate_path:
108-
path = tmpdir.join('a file')
109-
path.write('content')
110-
path = str(path)
111-
validate_path.return_value = path
112-
validate_file(path, str(tmpdir))
113-
validate_path.assert_called_with(path, str(tmpdir))
114-
115-
def test_it_rejects_directories(self, tmpdir):
116-
tmpdir.mkdir('directory')
117-
with raises(ValidationError) as excinfo:
118-
validate_file('directory', str(tmpdir))
119-
assert excinfo.value.code == INVALID_FILE
120-
121-
12279
class TestValidatePath:
12380

12481
def test_it_accepts_relative_path(self, tmpdir):
@@ -143,11 +100,6 @@ def test_it_only_accepts_strings(self):
143100
validate_path(None, '')
144101
assert excinfo.value.code == INVALID_STRING
145102

146-
def test_it_rejects_non_existent_path(self, tmpdir):
147-
with raises(ValidationError) as excinfo:
148-
validate_path('does not exist', str(tmpdir))
149-
assert excinfo.value.code == INVALID_PATH
150-
151103

152104
class TestValidateString:
153105

readthedocs/config/validation.py

+4-25
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Validations for the RTD configuration file."""
2+
43
import os
54

65

76
INVALID_BOOL = 'invalid-bool'
87
INVALID_CHOICE = 'invalid-choice'
98
INVALID_LIST = 'invalid-list'
109
INVALID_DICT = 'invalid-dictionary'
11-
INVALID_DIRECTORY = 'invalid-directory'
12-
INVALID_FILE = 'invalid-file'
1310
INVALID_PATH = 'invalid-path'
1411
INVALID_STRING = 'invalid-string'
1512
VALUE_NOT_FOUND = 'value-not-found'
@@ -22,8 +19,6 @@ class ValidationError(Exception):
2219
messages = {
2320
INVALID_BOOL: 'expected one of (0, 1, true, false), got {value}',
2421
INVALID_CHOICE: 'expected one of ({choices}), got {value}',
25-
INVALID_DIRECTORY: '{value} is not a directory',
26-
INVALID_FILE: '{value} is not a file',
2722
INVALID_DICT: '{value} is not a dictionary',
2823
INVALID_PATH: 'path {value} does not exist',
2924
INVALID_STRING: 'expected string',
@@ -79,29 +74,13 @@ def validate_bool(value):
7974
return bool(value)
8075

8176

82-
def validate_directory(value, base_path):
83-
"""Check that ``value`` is a directory."""
84-
path = validate_path(value, base_path)
85-
if not os.path.isdir(path):
86-
raise ValidationError(value, INVALID_DIRECTORY)
87-
return path
88-
89-
90-
def validate_file(value, base_path):
91-
"""Check that ``value`` is a file."""
92-
path = validate_path(value, base_path)
93-
if not os.path.isfile(path):
94-
raise ValidationError(value, INVALID_FILE)
95-
return path
96-
97-
9877
def validate_path(value, base_path):
99-
"""Check that ``value`` is an existent file in ``base_path``."""
78+
"""Check that ``value`` is a valid path name and normamlize it."""
10079
string_value = validate_string(value)
80+
if not string_value:
81+
raise ValidationError(value, INVALID_PATH)
10182
pathed_value = os.path.join(base_path, string_value)
10283
final_value = os.path.abspath(pathed_value)
103-
if not os.path.exists(final_value):
104-
raise ValidationError(value, INVALID_PATH)
10584
return final_value
10685

10786

0 commit comments

Comments
 (0)