Skip to content

Commit cdb64d1

Browse files
stsewdericholscher
andcommitted
Config file: use string for python.version (#8372)
Co-authored-by: Eric Holscher <[email protected]>
1 parent ed961db commit cdb64d1

File tree

7 files changed

+103
-87
lines changed

7 files changed

+103
-87
lines changed

docs/config-file/v2.rst

+14-9
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Below is an example YAML file which shows the most common configuration options:
3131
3232
# Optionally set the version of Python and requirements required to build your docs
3333
python:
34-
version: 3.7
34+
version: "3.7"
3535
install:
3636
- requirements: docs/requirements.txt
3737
@@ -51,7 +51,7 @@ Below is an example YAML file which shows the most common configuration options:
5151
5252
# Optionally set the version of Python and requirements required to build your docs
5353
python:
54-
version: 3.7
54+
version: "3.7"
5555
install:
5656
- requirements: docs/requirements.txt
5757
@@ -138,7 +138,7 @@ Configuration of the Python environment to be used.
138138
version: 2
139139
140140
python:
141-
version: 3.7
141+
version: "3.7"
142142
install:
143143
- requirements: docs/requirements.txt
144144
- method: pip
@@ -154,13 +154,18 @@ python.version
154154

155155
The Python version (this depends on :ref:`config-file/v2:build.image`).
156156

157-
:Type: ``number``
157+
:Type: ``string``
158158
:Default: ``3``
159159

160+
.. note::
161+
162+
Make sure to use quotes (``"``) to make it a string.
163+
We previously supported using numbers here,
164+
but that approach is deprecated.
160165
.. warning::
161166

162-
If you are using a :ref:`Conda <config-file/v2:conda>` environment to manage
163-
the build, this setting will not have any effect, as the Python version is managed by Conda.
167+
If you are using a :ref:`Conda <config-file/v2:conda>` environment to manage
168+
the build, this setting will not have any effect, as the Python version is managed by Conda.
164169

165170
python.install
166171
``````````````
@@ -189,7 +194,7 @@ Example:
189194
version: 2
190195
191196
python:
192-
version: 3.7
197+
version: "3.7"
193198
install:
194199
- requirements: docs/requirements.txt
195200
- requirements: requirements.txt
@@ -237,7 +242,7 @@ Example:
237242
version: 2
238243
239244
python:
240-
version: 3.7
245+
version: "3.7"
241246
install:
242247
- method: pip
243248
path: .
@@ -308,7 +313,7 @@ Configuration for the documentation build process.
308313
- cmake
309314
310315
python:
311-
version: 3.7
316+
version: "3.7"
312317
313318
314319
build.image

readthedocs/config/config.py

+19-13
Original file line numberDiff line numberDiff line change
@@ -254,23 +254,22 @@ def validate(self):
254254

255255
@property
256256
def python_interpreter(self):
257-
ver = self.python_full_version
258-
if not ver or isinstance(ver, (int, float)) or ver == '3.10':
259-
return 'python{}'.format(ver)
260-
261-
# Allow to specify ``pypy3.5`` as Python interpreter
262-
return ver
257+
version = self.python_full_version
258+
if version.startswith('pypy'):
259+
# Allow to specify ``pypy3.5`` as Python interpreter
260+
return version
261+
return f'python{version}'
263262

264263
@property
265264
def python_full_version(self):
266-
ver = self.python.version
267-
if ver in [2, 3, '2', '3']:
265+
version = self.python.version
266+
if version in ['2', '3']:
268267
# use default Python version if user only set '2', or '3'
269268
return self.get_default_python_version_for_image(
270269
self.build.image,
271-
int(ver),
270+
version,
272271
)
273-
return ver
272+
return version
274273

275274
@property
276275
def valid_build_images(self):
@@ -454,7 +453,7 @@ def validate_python(self):
454453
"""Validates the ``python`` key, set default values it's necessary."""
455454
install_project = self.defaults.get('install_project', False)
456455
use_system_packages = self.defaults.get('use_system_packages', False)
457-
version = self.defaults.get('python_version', 2)
456+
version = self.defaults.get('python_version', '2')
458457
python = {
459458
'use_system_site_packages': use_system_packages,
460459
'install_with_pip': False,
@@ -513,8 +512,9 @@ def validate_python(self):
513512

514513
if 'version' in raw_python:
515514
with self.catch_validation_error('python.version'):
515+
version = str(raw_python['version'])
516516
python['version'] = validate_choice(
517-
raw_python['version'],
517+
version,
518518
self.get_valid_python_versions(),
519519
)
520520

@@ -828,7 +828,13 @@ def validate_python(self):
828828

829829
python = {}
830830
with self.catch_validation_error('python.version'):
831-
version = self.pop_config('python.version', 3)
831+
version = self.pop_config('python.version', '3')
832+
if version == 3.1:
833+
# Special case for ``python.version: 3.10``,
834+
# yaml will transform this to the numeric value of `3.1`.
835+
# Save some frustration to users.
836+
version = '3.10'
837+
version = str(version)
832838
python['version'] = validate_choice(
833839
version,
834840
self.get_valid_python_versions(),

readthedocs/config/tests/test_config.py

+43-31
Original file line numberDiff line numberDiff line change
@@ -341,18 +341,18 @@ class TestValidatePythonVersion:
341341
def test_it_defaults_to_a_valid_version(self):
342342
build = get_build_config({'python': {}})
343343
build.validate()
344-
assert build.python.version == 2
344+
assert build.python.version == '2'
345345
assert build.python_interpreter == 'python2.7'
346-
assert build.python_full_version == 2.7
346+
assert build.python_full_version == '2.7'
347347

348348
def test_it_supports_other_versions(self):
349349
build = get_build_config(
350350
{'python': {'version': 3.7}},
351351
)
352352
build.validate()
353-
assert build.python.version == 3.7
353+
assert build.python.version == '3.7'
354354
assert build.python_interpreter == 'python3.7'
355-
assert build.python_full_version == 3.7
355+
assert build.python_full_version == '3.7'
356356

357357
def test_it_supports_string_versions(self):
358358
build = get_build_config(
@@ -383,9 +383,9 @@ def test_it_validates_wrong_type(self):
383383

384384
def test_it_validates_env_supported_versions(self):
385385
build = get_build_config(
386-
{'python': {'version': 3.6}},
386+
{'python': {'version': '3.6'}},
387387
env_config={
388-
'python': {'supported_versions': [3.5]},
388+
'python': {'supported_versions': ['3.5']},
389389
'build': {'image': 'custom'},
390390
},
391391
)
@@ -395,18 +395,18 @@ def test_it_validates_env_supported_versions(self):
395395
assert excinfo.value.code == INVALID_CHOICE
396396

397397
build = get_build_config(
398-
{'python': {'version': 3.6}},
398+
{'python': {'version': '3.6'}},
399399
env_config={
400-
'python': {'supported_versions': [3.5, 3.6]},
400+
'python': {'supported_versions': ['3.5', '3.6']},
401401
'build': {'image': 'custom'},
402402
},
403403
)
404404
build.validate()
405-
assert build.python.version == 3.6
405+
assert build.python.version == '3.6'
406406
assert build.python_interpreter == 'python3.6'
407-
assert build.python_full_version == 3.6
407+
assert build.python_full_version == '3.6'
408408

409-
@pytest.mark.parametrize('value', [2, 3])
409+
@pytest.mark.parametrize('value', ['2', '3'])
410410
def test_it_respects_default_value(self, value):
411411
defaults = {
412412
'python_version': value,
@@ -724,7 +724,7 @@ def test_as_dict(tmpdir):
724724
'version': '1',
725725
'formats': ['pdf'],
726726
'python': {
727-
'version': 3.7,
727+
'version': '3.7',
728728
'install': [{
729729
'requirements': 'requirements.txt',
730730
}],
@@ -986,8 +986,8 @@ def test_python_check_invalid_types(self, value):
986986
@pytest.mark.parametrize(
987987
'image,versions',
988988
[
989-
('latest', [2, 2.7, 3, 3.5, 3.6, 3.7, 'pypy3.5']),
990-
('stable', [2, 2.7, 3, 3.5, 3.6, 3.7]),
989+
('latest', ['2', '2.7', '3', '3.5', '3.6', '3.7', 'pypy3.5']),
990+
('stable', ['2', '2.7', '3', '3.5', '3.6', '3.7']),
991991
],
992992
)
993993
def test_python_version(self, image, versions):
@@ -1015,13 +1015,25 @@ def test_python_version_accepts_string(self):
10151015
build.validate()
10161016
assert build.python.version == '3.6'
10171017

1018-
def test_python_version_310(self):
1018+
def test_python_version_accepts_number(self):
1019+
build = self.get_build_config({
1020+
'build': {
1021+
'image': 'latest',
1022+
},
1023+
'python': {
1024+
'version': 3.6,
1025+
},
1026+
})
1027+
build.validate()
1028+
assert build.python.version == '3.6'
1029+
1030+
def test_python_version_310_as_number(self):
10191031
build = self.get_build_config({
10201032
'build': {
10211033
'image': 'testing',
10221034
},
10231035
'python': {
1024-
'version': '3.10',
1036+
'version': 3.10,
10251037
},
10261038
})
10271039
build.validate()
@@ -1051,27 +1063,27 @@ def test_python_version_invalid(self, image, versions):
10511063
def test_python_version_default(self):
10521064
build = self.get_build_config({})
10531065
build.validate()
1054-
assert build.python.version == 3
1066+
assert build.python.version == '3'
10551067

10561068
@pytest.mark.parametrize(
1057-
'image,default_version',
1069+
'image, default_version, full_version',
10581070
[
1059-
('2.0', 3.5),
1060-
('4.0', 3.7),
1061-
('5.0', 3.7),
1062-
('latest', 3.7),
1063-
('stable', 3.7),
1071+
('2.0', '3', '3.5'),
1072+
('4.0', '3', '3.7'),
1073+
('5.0', '3', '3.7'),
1074+
('latest', '3', '3.7'),
1075+
('stable', '3', '3.7'),
10641076
],
10651077
)
1066-
def test_python_version_default_from_image(self, image, default_version):
1078+
def test_python_version_default_from_image(self, image, default_version, full_version):
10671079
build = self.get_build_config({
10681080
'build': {
10691081
'image': image,
10701082
},
10711083
})
10721084
build.validate()
1073-
assert build.python.version == int(default_version) # 2 or 3
1074-
assert build.python_full_version == default_version
1085+
assert build.python.version == default_version
1086+
assert build.python_full_version == full_version
10751087

10761088
@pytest.mark.parametrize('value', [2, 3])
10771089
def test_python_version_overrides_default(self, value):
@@ -1080,13 +1092,13 @@ def test_python_version_overrides_default(self, value):
10801092
{'defaults': {'python_version': value}},
10811093
)
10821094
build.validate()
1083-
assert build.python.version == 3
1095+
assert build.python.version == '3'
10841096

1085-
@pytest.mark.parametrize('value', [2, 3, 3.6])
1097+
@pytest.mark.parametrize('value', ['2', '3', '3.6'])
10861098
def test_python_version_priority_over_default(self, value):
10871099
build = self.get_build_config(
10881100
{'python': {'version': value}},
1089-
{'defaults': {'python_version': 3}},
1101+
{'defaults': {'python_version': '3'}},
10901102
)
10911103
build.validate()
10921104
assert build.python.version == value
@@ -2104,7 +2116,7 @@ def test_as_dict(self, tmpdir):
21042116
'version': 2,
21052117
'formats': ['pdf'],
21062118
'python': {
2107-
'version': 3.6,
2119+
'version': '3.6',
21082120
'install': [{
21092121
'requirements': 'requirements.txt',
21102122
}],
@@ -2117,7 +2129,7 @@ def test_as_dict(self, tmpdir):
21172129
'version': '2',
21182130
'formats': ['pdf'],
21192131
'python': {
2120-
'version': 3.6,
2132+
'version': '3.6',
21212133
'install': [{
21222134
'requirements': 'requirements.txt',
21232135
}],

readthedocs/doc_builder/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def load_yaml_config(version):
2424
# can be rejected at validation
2525

2626
img_name = project.container_image or DOCKER_IMAGE
27-
python_version = 3 if project.python_interpreter == 'python3' else 2
27+
python_version = '3' if project.python_interpreter == 'python3' else '2'
2828
try:
2929
sphinx_configuration = path.join(
3030
version.get_conf_py_path(),

0 commit comments

Comments
 (0)