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

Allow documentation to be build without installing and configuring AiiDA #3669

Merged
merged 2 commits into from
Dec 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 0 additions & 12 deletions aiida/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,6 @@
)
__paper_short__ = 'G. Pizzi et al., Comp. Mat. Sci 111, 218 (2016).'

# Configure the default logging
configure_logging()

if get_config_option('warnings.showdeprecations'):
# If the user does not want to get AiiDA deprecation warnings, we disable them - this can be achieved with::
# verdi config warnings.showdeprecations False
# Note that the AiidaDeprecationWarning does NOT inherit from DeprecationWarning
warnings.simplefilter('default', AiidaDeprecationWarning) # pylint: disable=no-member
# This should default to 'once', i.e. once per different message
else:
warnings.simplefilter('ignore', AiidaDeprecationWarning) # pylint: disable=no-member


def load_dbenv(profile=None):
"""Alias for `load_dbenv` from `aiida.backends.utils`
Expand Down
88 changes: 56 additions & 32 deletions aiida/manage/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
###########################################################################
# pylint: disable=undefined-variable,wildcard-import,global-statement,redefined-outer-name,cyclic-import
"""Modules related to the configuration of an AiiDA instance."""
import warnings

from aiida.common.warnings import AiidaDeprecationWarning
from .config import *
from .options import *
from .profile import *
Expand All @@ -18,9 +20,6 @@
PROFILE = None
BACKEND_UUID = None # This will be set to the UUID of the profile as soon as its corresponding backend is loaded

# This is used (and should be set to true) for the correct compilation of the documentation on readthedocs
IN_RT_DOC_MODE = False

__all__ = (
config.__all__ + options.__all__ + profile.__all__ +
('get_config', 'get_config_option', 'load_profile', 'reset_config')
Expand Down Expand Up @@ -68,7 +67,10 @@ def load_profile(profile=None):


def load_config(create=False):
"""Instantiate the Config object representing the configuration file of the current AiiDA instance.
"""Instantiate Config object representing an AiiDA configuration file.

Warning: Contrary to :func:`~aiida.manage.configuration.get_config`, this function is uncached and will always
create a new Config object. You may want to call :func:`~aiida.manage.configuration.get_config` instead.

:param create: if True, will create the configuration file if it does not already exist
:type create: bool
Expand All @@ -84,31 +86,6 @@ def load_config(create=False):

filepath = os.path.join(AIIDA_CONFIG_FOLDER, DEFAULT_CONFIG_FILE_NAME)

if IN_RT_DOC_MODE:
# The following is a dummy config.json configuration that it is used for the
# proper compilation of the documentation on readthedocs.
from aiida.manage.external.postgres import DEFAULT_DBINFO
import tempfile
return Config(
tempfile.mkstemp()[1], {
'default_profile': 'default',
'profiles': {
'default': {
'AIIDADB_ENGINE': 'postgresql_psycopg2',
'AIIDADB_BACKEND': 'django',
'AIIDADB_HOST': DEFAULT_DBINFO['host'],
'AIIDADB_PORT': DEFAULT_DBINFO['port'],
'AIIDADB_NAME': 'aiidadb',
'AIIDADB_PASS': '123',
'default_user_email': 'aiida@epfl.ch',
'TIMEZONE': 'Europe/Zurich',
'AIIDADB_REPOSITORY_URI': 'file:///tmp/repository',
'AIIDADB_USER': 'aiida'
}
}
}
)

if not os.path.isfile(filepath) and not create:
raise exceptions.MissingConfigurationError('configuration file {} does not exist'.format(filepath))

Expand Down Expand Up @@ -155,10 +132,13 @@ def reset_config():
def get_config(create=False):
"""Return the current configuration.

If the configuration has not been loaded yet, it will be loaded first and then returned.
If the configuration has not been loaded yet
* the configuration is loaded using ``load_config``
* the global `CONFIG` variable is set
* the configuration object is returned

Note: this function should only be called by parts of the code that expect that a complete AiiDA instance exists,
i.e. an AiiDA folder exists and contains a valid configuration file.
Note: This function will except if no configuration file can be found. Only call this function, if you need
information from the configuration file.

:param create: if True, will create the configuration file if it does not already exist
:type create: bool
Expand All @@ -172,6 +152,15 @@ def get_config(create=False):
if not CONFIG:
CONFIG = load_config(create=create)

if CONFIG.get_option('warnings.showdeprecations'):
# If the user does not want to get AiiDA deprecation warnings, we disable them - this can be achieved with::
# verdi config warnings.showdeprecations False
# Note that the AiidaDeprecationWarning does NOT inherit from DeprecationWarning
warnings.simplefilter('default', AiidaDeprecationWarning) # pylint: disable=no-member
# This should default to 'once', i.e. once per different message
else:
warnings.simplefilter('ignore', AiidaDeprecationWarning) # pylint: disable=no-member

return CONFIG


Expand Down Expand Up @@ -208,3 +197,38 @@ def get_config_option(option_name):
value = value_profile if value_profile else config.get_option(option_name)

return value


def load_documentation_profile():
"""Load a dummy profile just for the purposes of being able to build the documentation.

The building of the documentation will require importing the `aiida` package and some code will try to access the
loaded configuration and profile, which if not done will except. On top of that, Django will raise an exception if
the database models are loaded before its settings are loaded. This also is taken care of by loading a Django
profile and loading the corresponding backend. Calling this function will perform all these requirements allowing
the documentation to be built without having to install and configure AiiDA nor having an actual database present.
"""
import tempfile
from aiida.manage.manager import get_manager
from .config import Config
from .profile import Profile

global PROFILE
global CONFIG

with tempfile.NamedTemporaryFile() as handle:
profile_name = 'readthedocs'
profile = {
'AIIDADB_ENGINE': 'postgresql_psycopg2',
'AIIDADB_BACKEND': 'django',
'AIIDADB_PORT': 5432,
'AIIDADB_HOST': 'localhost',
'AIIDADB_NAME': 'aiidadb',
'AIIDADB_PASS': 'aiidadb',
'AIIDADB_USER': 'aiida',
'AIIDADB_REPOSITORY_URI': 'file:///dev/null',
}
config = {'default_profile': profile_name, 'profiles': {profile_name: profile}}
PROFILE = Profile(profile_name, profile, from_config=True)
CONFIG = Config(handle.name, config)
get_manager()._load_backend(schema_check=False) # pylint: disable=protected-access
83 changes: 0 additions & 83 deletions docs/rtd_settings.py

This file was deleted.

53 changes: 12 additions & 41 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import os
import sys

# Following 3 lines avoid the need of importing load_dbenv() for compiling the documentation -> works also without verdi install
# Add `aiida` to the path so it can be imported without installing it
sys.path.append(os.path.join(os.path.split(__file__)[0], os.pardir, os.pardir))
sys.path.append(os.path.join(os.path.split(__file__)[0], os.pardir))
os.environ['DJANGO_SETTINGS_MODULE'] = 'rtd_settings'

import aiida
from aiida.manage.configuration import load_documentation_profile

# Load the dummy profile even if we are running locally, this way the documentation will succeed even if the current
# default profile of the AiiDA installation does not use a Django backend.
load_documentation_profile()

# If we are not on READTHEDOCS load the Sphinx theme manually
if not os.environ.get('READTHEDOCS', None):
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
Expand Down Expand Up @@ -234,43 +242,6 @@
# If false, no module index is generated.
#latex_domain_indices = True

# on_rtd is whether we are on readthedocs.org, this line of code grabbed
# from docs.readthedocs.org
# NOTE: it is needed to have these lines before load_dbenv()
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'

if not on_rtd: # only import and set the theme if we're building docs locally
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
# No sphinx_rtd_theme installed
pass
# Load the database environment by first loading the profile and then loading the backend through the manager
from aiida.manage.configuration import get_config, load_profile
from aiida.manage.manager import get_manager
config = get_config()
load_profile(config.default_profile_name)
get_manager().get_backend()
else:
from aiida.manage import configuration
from aiida.manage.configuration import load_profile, reset_config
from aiida.manage.manager import get_manager

# Set the global variable to trigger shortcut behavior in `aiida.manager.configuration.load_config`
configuration.IN_RT_DOC_MODE = True

# First need to reset the config, because an empty one will have been loaded when `aiida` module got imported
reset_config()

# Load the profile: this will first load the config, which will be the dummy one for RTD purposes
load_profile()

# Finally load the database backend but without checking the schema because there is no actual database
get_manager()._load_backend(schema_check=False)


def run_apidoc(_):
"""Runs sphinx-apidoc when building the documentation.

Expand Down