Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement extended install option #4740

Merged
merged 35 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
09c0c87
Extend validation for config.install
stsewd Oct 8, 2018
c817d4b
Use new models
stsewd Oct 8, 2018
dae2c8b
Fix tests for v1
stsewd Oct 8, 2018
fb25527
More checks
stsewd Oct 9, 2018
f98103f
Fix tests for v2
stsewd Oct 9, 2018
258dff3
More tests
stsewd Oct 9, 2018
8974822
Always install from requirements file on v1
stsewd Oct 9, 2018
74f8470
Always use absolute path
stsewd Oct 9, 2018
bb81a7b
Refactor rtd code to use multiple installs
stsewd Oct 9, 2018
6897f07
Fix tests
stsewd Oct 9, 2018
2987a33
Fix tests from config_integration
stsewd Oct 10, 2018
4b7ca7f
Fix test for doc_building
stsewd Oct 10, 2018
3fb5964
Fix linter
stsewd Oct 10, 2018
789dff3
New test for multiple installs
stsewd Oct 10, 2018
058fadd
Install from local path
stsewd Oct 10, 2018
46c2ffb
Refactoring and docstrings
stsewd Oct 10, 2018
39d6e3b
Move comment
stsewd Oct 10, 2018
f99afe4
Mores tests
stsewd Oct 10, 2018
d45aa02
Merge branch 'master' into implement-extend-install-option
stsewd Oct 24, 2018
52b4666
Merge branch 'master' into implement-extend-install-option
stsewd Oct 31, 2018
7a7cb4c
Merge branch 'master' into implement-extend-install-option
stsewd Nov 14, 2018
fdf84a0
Use classes instead of namedtuples for config models
stsewd Nov 14, 2018
8f2f89c
Move to_dict to utils
stsewd Nov 15, 2018
88581af
Merge branch 'master' into implement-extend-install-option
stsewd Dec 5, 2018
a6b7c97
Move function to utils
stsewd Dec 5, 2018
d194198
Add explanation about using __slots__
stsewd Dec 5, 2018
b555259
Docstrings
stsewd Dec 5, 2018
9c677a4
Better tests
stsewd Dec 5, 2018
4159fed
Don't return
stsewd Dec 5, 2018
808e83b
Don't touch original config
stsewd Dec 5, 2018
0df97dc
Better docstrings and comments
stsewd Dec 6, 2018
823a9dc
Merge branch 'master' into implement-extend-install-option
stsewd Jan 15, 2019
b2939b4
Merge branch 'master' into implement-extend-install-option
stsewd Jan 21, 2019
0345845
Fix merge
stsewd Jan 21, 2019
296e365
Merge branch 'master' into implement-extend-install-option
stsewd Jan 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 141 additions & 36 deletions readthedocs/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@
from readthedocs.projects.constants import DOCUMENTATION_CHOICES

from .find import find_one
from .models import Build, Conda, Mkdocs, Python, Sphinx, Submodules
from .models import (
Build,
Conda,
Mkdocs,
Python,
PythonInstall,
PythonInstallRequirements,
Sphinx,
Submodules,
)
from .parser import ParseError, parse
from .validation import (
VALUE_NOT_FOUND,
Expand All @@ -36,10 +45,14 @@
'ConfigError',
'ConfigOptionNotSupportedError',
'InvalidConfig',
'PIP',
'ProjectConfig',
'SETUPTOOLS',
)

ALL = 'all'
PIP = 'pip'
SETUPTOOLS = 'setuptools'
CONFIG_FILENAME_REGEX = r'^\.?readthedocs.ya?ml$'

CONFIG_NOT_SUPPORTED = 'config-not-supported'
Expand Down Expand Up @@ -80,6 +93,15 @@
}


def _list_to_dict(list_):
stsewd marked this conversation as resolved.
Show resolved Hide resolved
"""Transform a list to a dictionary with its indices as keys."""
dict_ = {
str(i): element
for i, element in enumerate(list_)
}
return dict_


class ConfigError(Exception):

"""Base error for the rtd configuration file."""
Expand Down Expand Up @@ -507,7 +529,7 @@ def validate_requirements_file(self):
return None
base_path = os.path.dirname(self.source_file)
with self.catch_validation_error('requirements_file'):
validate_file(requirements_file, base_path)
requirements_file = validate_file(requirements_file, base_path)
return requirements_file

def validate_formats(self):
Expand Down Expand Up @@ -548,9 +570,39 @@ def formats(self):
@property
def python(self):
"""Python related configuration."""
python = self._config['python']
requirements = self._config['requirements_file']
self._config['python']['requirements'] = requirements
return Python(**self._config['python'])
python_install = []

# Alwyas append a `PythonInstallRequirements` option.
stsewd marked this conversation as resolved.
Show resolved Hide resolved
# If requirements is None, rtd will try to find a requirements file.
python_install.append(
PythonInstallRequirements(
requirements=requirements,
)
)
if python['install_with_pip']:
python_install.append(
PythonInstall(
path=self.base_path,
method=PIP,
extra_requirements=python['extra_requirements'],
)
)
elif python['install_with_setup']:
python_install.append(
PythonInstall(
path=self.base_path,
method=SETUPTOOLS,
extra_requirements=[],
)
)

return Python(
version=python['version'],
install=python_install,
use_system_site_packages=python['use_system_site_packages'],
)

@property
def conda(self):
Expand Down Expand Up @@ -602,7 +654,7 @@ class BuildConfigV2(BuildConfigBase):
valid_formats = ['htmlzip', 'pdf', 'epub']
valid_build_images = ['1.0', '2.0', '3.0', 'stable', 'latest']
default_build_image = 'latest'
valid_install_options = ['pip', 'setup.py']
valid_install_method = [PIP, SETUPTOOLS]
valid_sphinx_builders = {
'html': 'sphinx',
'htmldir': 'sphinx_htmldir',
Expand Down Expand Up @@ -725,38 +777,22 @@ def validate_python(self):
self.get_valid_python_versions(),
)

with self.catch_validation_error('python.requirements'):
requirements = self.defaults.get('requirements_file')
requirements = self.pop_config('python.requirements', requirements)
if requirements != '' and requirements is not None:
requirements = validate_file(requirements, self.base_path)
python['requirements'] = requirements

with self.catch_validation_error('python.install'):
install = (
'setup.py' if self.defaults.get('install_project') else None
)
install = self.pop_config('python.install', install)
if install is not None:
validate_choice(install, self.valid_install_options)
python['install_with_setup'] = install == 'setup.py'
python['install_with_pip'] = install == 'pip'

with self.catch_validation_error('python.extra_requirements'):
extra_requirements = self.pop_config(
'python.extra_requirements', []
)
extra_requirements = validate_list(extra_requirements)
if extra_requirements and not python['install_with_pip']:
self.error(
'python.extra_requirements',
'You need to install your project with pip '
'to use extra_requirements',
code=PYTHON_INVALID,
raw_install = self.raw_config.get('python', {}).get('install', [])
validate_list(raw_install)
if raw_install:
# Transform to a dict, so it's easy to validate extra keys.
self.raw_config.setdefault('python', {})['install'] = (
_list_to_dict(raw_install)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ what my comment says

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not a good idea to change the raw_config object. By doing this, it won't be "raw" anymore and it will bring potential confusions.

I understand that the raw_config is the user's YAML parsed into a Python object (without being touched by us).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, checking the validate_python_install I think this can be accessed by an index instead of a key without need to modify the original config object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't access by the index because of pop_config, it needs to pop recursively, and we need to keep the track of the index (if index 3 is deleted, the index 4 passes to be 3, we don't want that). I guess I could create a copy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I see what you mean.

I don't really like the solution because it modifies the original structure and it's confusing to me but on the other hand, I don't have a better idea in my mind at the moment :(

)
python['extra_requirements'] = [
validate_string(extra) for extra in extra_requirements
]
else:
self.pop_config('python.install')

raw_install = self.raw_config.get('python', {}).get('install', [])
python['install'] = [
self.validate_python_install(index)
for index in range(len(raw_install))
]

with self.catch_validation_error('python.system_packages'):
system_packages = self.defaults.get(
Expand All @@ -771,6 +807,60 @@ def validate_python(self):

return python

def validate_python_install(self, index):
"""Validates the python.install.{index} key."""
python_install = {}
key = 'python.install.{}'.format(index)
agjohnson marked this conversation as resolved.
Show resolved Hide resolved
raw_install = self.raw_config['python']['install'][str(index)]
with self.catch_validation_error(key):
validate_dict(raw_install)

if 'requirements' in raw_install:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could likely use constants for requirements and path, but not worth blocking this PR on.

requirements_key = key + '.requirements'
with self.catch_validation_error(requirements_key):
requirements = validate_file(
self.pop_config(requirements_key),
self.base_path
)
python_install['requirements'] = requirements
elif 'path' in raw_install:
path_key = key + '.path'
with self.catch_validation_error(path_key):
path = validate_directory(
self.pop_config(path_key),
self.base_path
)
python_install['path'] = path

method_key = key + '.method'
with self.catch_validation_error(method_key):
method = validate_choice(
self.pop_config(method_key, PIP),
self.valid_install_method
)
python_install['method'] = method

extra_req_key = key + '.extra_requirements'
with self.catch_validation_error(extra_req_key):
extra_requirements = validate_list(
self.pop_config(extra_req_key, [])
)
if extra_requirements and python_install['method'] != PIP:
self.error(
extra_req_key,
'You need to install your project with pip '
'to use extra_requirements',
code=PYTHON_INVALID,
)
python_install['extra_requirements'] = extra_requirements
agjohnson marked this conversation as resolved.
Show resolved Hide resolved
else:
self.error(
key,
'"path" or "requirements" key is required',
code=CONFIG_REQUIRED,
)
return python_install

def get_valid_python_versions(self):
"""
Get the valid python versions for the current docker image.
Expand Down Expand Up @@ -1007,7 +1097,22 @@ def build(self):

@property
def python(self):
return Python(**self._config['python'])
python_install = []
python = self._config['python']
for install in python['install']:
if 'requirements' in install:
python_install.append(
PythonInstallRequirements(**install)
)
elif 'path' in install:
python_install.append(
PythonInstall(**install)
)
return Python(
version=python['version'],
install=python_install,
use_system_site_packages=python['use_system_site_packages'],
)

@property
def sphinx(self):
Expand Down
19 changes: 16 additions & 3 deletions readthedocs/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,24 @@
'Python',
[
'version',
'install',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a list, not completely satisfy with this name, any suggestion for rename or if it's fine to let that name are welcome p:

'use_system_site_packages',
],
)

PythonInstallRequirements = namedtuple(
'PythonInstallRequirements',
[
'requirements',
'install_with_pip',
'install_with_setup',
],
)

PythonInstall = namedtuple(
'PythonInstall',
[
'path',
'method',
'extra_requirements',
'use_system_site_packages',
],
)

Expand Down
Loading