Skip to content

Commit

Permalink
initial work at multiple .readthedocs.yml files per repo
Browse files Browse the repository at this point in the history
  • Loading branch information
ewdurbin committed Feb 7, 2023
1 parent ce1a81a commit fb84ad8
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 4 deletions.
1 change: 1 addition & 0 deletions readthedocs/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Meta(ProjectSerializer.Meta):
'show_advertising',
'environment_variables',
'max_concurrent_builds',
'rtd_conf_file',
)


Expand Down
9 changes: 7 additions & 2 deletions readthedocs/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1369,15 +1369,20 @@ def search(self):
return Search(**self._config['search'])


def load(path, env_config):
def load(path, env_config, config_file=None):
"""
Load a project configuration and the top-most build config for a given path.
That is usually the root of the project, but will look deeper. According to
the version of the configuration a build object would be load and validated,
``BuildConfigV1`` is the default.
"""
filename = find_one(path, CONFIG_FILENAME_REGEX)
if config_file is None or config_file == '':
filename = find_one(path, CONFIG_FILENAME_REGEX)
else:
filename = os.path.join(path, config_file)
if not os.path.exists(filename):
raise ConfigError(f".readthedocs.yml not found at {config_file}", CONFIG_FILE_REQUIRED)

if not filename:
raise ConfigFileNotFound(path)
Expand Down
1 change: 1 addition & 0 deletions readthedocs/doc_builder/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def load_yaml_config(version):
config = load_config(
path=checkout_path,
env_config=env_config,
config_file=project.rtd_conf_file,
)
except ConfigFileNotFound:
# Default to use v1 with some defaults from the web interface
Expand Down
12 changes: 12 additions & 0 deletions readthedocs/projects/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class Meta:
'single_version',
'external_builds_enabled',
'external_builds_privacy_level',
'rtd_conf_file',
)
# These that can be set per-version using a config file.
per_version_settings = (
Expand Down Expand Up @@ -357,6 +358,17 @@ def clean_conf_py_file(self):
) # yapf: disable
return filename

def clean_rtd_conf_file(self):
filename = self.cleaned_data.get('rtd_conf_file', '').strip()
if filename and '.readthedocs.yml' not in filename:
raise forms.ValidationError(
_(
'Your configuration file is invalid, make sure it contains '
'.readthedocs.yml in it.',
),
) # yapf: disable
return filename

def get_all_active_versions(self):
"""
Returns all active versions.
Expand Down
23 changes: 23 additions & 0 deletions readthedocs/projects/migrations/0096_auto_20230207_1642.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.17 on 2023-02-07 16:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('projects', '0095_default_branch_helptext'),
]

operations = [
migrations.AddField(
model_name='historicalproject',
name='rtd_conf_file',
field=models.CharField(blank=True, default='', help_text='Path from project root to <code>.readthedocs.yml</code> file (ex. <code>docs/.readthedocs.yml</code>). Leave blank if you want us to find it for you.', max_length=255, verbose_name='.readthedocs.yml configuration file'),
),
migrations.AddField(
model_name='project',
name='rtd_conf_file',
field=models.CharField(blank=True, default='', help_text='Path from project root to <code>.readthedocs.yml</code> file (ex. <code>docs/.readthedocs.yml</code>). Leave blank if you want us to find it for you.', max_length=255, verbose_name='.readthedocs.yml configuration file'),
),
]
11 changes: 11 additions & 0 deletions readthedocs/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,17 @@ class Project(models.Model):
'Leave blank if you want us to find it for you.',
),
)
rtd_conf_file = models.CharField(
_('.readthedocs.yml configuration file'),
max_length=255,
default='',
blank=True,
help_text=_(
'Path from project root to <code>.readthedocs.yml</code> file '
'(ex. <code>docs/.readthedocs.yml</code>). '
'Leave blank if you want us to find it for you.',
),
)

featured = models.BooleanField(_('Featured'), default=False)
skip = models.BooleanField(_('Skip'), default=False)
Expand Down
6 changes: 5 additions & 1 deletion readthedocs/rtd_tests/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,19 +686,22 @@ class APITests(TestCase):
def test_user_doesnt_get_full_api_return(self):
user_normal = get(User, is_staff=False)
user_admin = get(User, is_staff=True)
project = get(Project, main_language_project=None, conf_py_file='foo')
project = get(Project, main_language_project=None, conf_py_file='foo', rtd_conf_file='bar')
client = APIClient()

client.force_authenticate(user=user_normal)
resp = client.get('/api/v2/project/%s/' % (project.pk))
self.assertEqual(resp.status_code, 200)
self.assertNotIn('conf_py_file', resp.data)
self.assertNotIn('rtd_conf_file', resp.data)

client.force_authenticate(user=user_admin)
resp = client.get('/api/v2/project/%s/' % (project.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('conf_py_file', resp.data)
self.assertEqual(resp.data['conf_py_file'], 'foo')
self.assertIn('rtd_conf_file', resp.data)
self.assertEqual(resp.data['rtd_conf_file'], 'bar')

def test_project_features(self):
user = get(User, is_staff=True)
Expand Down Expand Up @@ -2453,6 +2456,7 @@ def test_get_version_by_id(self):
"repo": "https://github.com/pypa/pip",
"repo_type": "git",
"requirements_file": None,
"rtd_conf_file": "",
"show_advertising": True,
"skip": False,
"slug": "pip",
Expand Down
3 changes: 2 additions & 1 deletion readthedocs/rtd_tests/tests/test_config_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def create_load(config=None):
if config is None:
config = {}

def inner(path=None, env_config=None):
def inner(path=None, env_config=None, config_file=None):
env_config_defaults = {
'output_base': '',
'name': '1',
Expand Down Expand Up @@ -99,6 +99,7 @@ def test_python_supported_versions_default_image_1_0(self, load_config):
load_config.assert_called_once_with(
path=mock.ANY,
env_config=expected_env_config,
config_file='',
)
self.assertEqual(config.python.version, '3')

Expand Down

0 comments on commit fb84ad8

Please sign in to comment.