Skip to content

Commit

Permalink
Refactor: Delay import of heavy packages to speed up import time (#6116)
Browse files Browse the repository at this point in the history
The importing of packages `urllib`, `yaml` and `pgsu` are moved from
top-level to inside the scopes where they are needed. This significantly
improves the load time of the `aiida` package and its subpackages.

The import of `Transport` in the `aiida.engine` package also slows down
imports, but it is only used for type checking, so its import is placed
inside the `if TYPE_CHECKING` guard.

Finally, the `DEFAULT_DBINFO`, `Postgres` and `PostgresConnectionMode`
objects of the `aiida.manage.external.postgres` package are no longer
exposed on the top-level as this also slows down imports. This is a
breaking change technically, but these resources should not be used by
downstream packages.
  • Loading branch information
danielhollas authored Sep 12, 2023
1 parent aeaa90f commit 5dda6fd
Show file tree
Hide file tree
Showing 19 changed files with 53 additions and 30 deletions.
3 changes: 2 additions & 1 deletion aiida/cmdline/commands/cmd_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from pathlib import Path
import traceback
from typing import List, Tuple
import urllib.request

import click
from click_spinner import spinner
Expand Down Expand Up @@ -432,6 +431,8 @@ def _import_archive_and_migrate(
:param try_migration: whether to try a migration if the import raises `IncompatibleStorageSchema`
"""
import urllib.request

from aiida.common.folders import SandboxFolder
from aiida.tools.archive.abstract import get_format
from aiida.tools.archive.imports import import_archive as _import_archive
Expand Down
3 changes: 2 additions & 1 deletion aiida/cmdline/commands/cmd_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

import click
import tabulate
import yaml

from aiida.cmdline.commands.cmd_verdi import verdi
from aiida.cmdline.groups.dynamic import DynamicEntryPointCommandGroup
Expand Down Expand Up @@ -242,6 +241,8 @@ def show(code):
@with_dbenv()
def export(code, output_file):
"""Export code to a yaml file."""
import yaml

code_data = {}

for key in code.get_cli_options().keys():
Expand Down
14 changes: 12 additions & 2 deletions aiida/cmdline/commands/cmd_devel.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,18 @@ def devel_check_undesired_imports():
loaded_modules = 0

for modulename in [
'asyncio', 'requests', 'plumpy', 'disk_objectstore', 'paramiko', 'seekpath', 'CifFile', 'ase', 'pymatgen',
'spglib', 'pymysql'
'asyncio',
'requests',
'plumpy',
'disk_objectstore',
'paramiko',
'seekpath',
'CifFile',
'ase',
'pymatgen',
'spglib',
'pymysql',
'yaml',
]:
if modulename in sys.modules:
echo.echo_warning(f'Detected loaded module "{modulename}"')
Expand Down
3 changes: 2 additions & 1 deletion aiida/cmdline/commands/cmd_rabbitmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import click
import tabulate
import wrapt
import yaml

from aiida.cmdline.commands.cmd_devel import verdi_devel
from aiida.cmdline.params import arguments, options
Expand Down Expand Up @@ -146,6 +145,8 @@ def with_manager(wrapped, _, args, kwargs):
@with_manager
def cmd_server_properties(manager):
"""List the server properties."""
import yaml

data = {}
for key, value in manager.get_communicator().server_properties.items():
data[key] = value.decode('utf-8') if isinstance(value, bytes) else value
Expand Down
3 changes: 2 additions & 1 deletion aiida/cmdline/params/options/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import typing as t

import click
import yaml

from .overridable import OverridableOption

Expand All @@ -33,6 +32,8 @@

def yaml_config_file_provider(handle, cmd_name): # pylint: disable=unused-argument
"""Read yaml config file from file handle."""
import yaml

return yaml.safe_load(handle)


Expand Down
2 changes: 1 addition & 1 deletion aiida/cmdline/params/options/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
###########################################################################
"""Module with pre-defined reusable commandline options that can be used as `click` decorators."""
import click
from pgsu import DEFAULT_DSN as DEFAULT_DBINFO # pylint: disable=no-name-in-module

from aiida.common.log import LOG_LEVELS, configure_logging
from aiida.manage.external.postgres import DEFAULT_DBINFO
from aiida.manage.external.rmq import BROKER_DEFAULTS

from .. import types
Expand Down
8 changes: 6 additions & 2 deletions aiida/cmdline/params/types/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
"""Click parameter types for paths."""
import os
from socket import timeout
import urllib.error
import urllib.request

import click

Expand Down Expand Up @@ -88,6 +86,9 @@ def convert(self, value, param, ctx):

def checks_url(self, url, param, ctx):
"""Check whether URL is reachable within timeout."""
import urllib.error
import urllib.request

try:
with urllib.request.urlopen(url, timeout=self.timeout_seconds):
pass
Expand Down Expand Up @@ -123,6 +124,9 @@ def convert(self, value, param, ctx):

def get_url(self, url, param, ctx):
"""Retrieve file from URL."""
import urllib.error
import urllib.request

try:
return urllib.request.urlopen(url, timeout=self.timeout_seconds) # pylint: disable=consider-using-with
except (urllib.error.URLError, urllib.error.HTTPError, timeout):
Expand Down
5 changes: 4 additions & 1 deletion aiida/cmdline/utils/echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from typing import Any, Optional

import click
import yaml

CMDLINE_LOGGER = logging.getLogger('verdi')

Expand Down Expand Up @@ -225,11 +224,15 @@ def default_jsondump(data):

def _format_yaml(dictionary, sort_keys=True):
"""Return a dictionary formatted as a string using the YAML format."""
import yaml

return yaml.dump(dictionary, sort_keys=sort_keys)


def _format_yaml_expanded(dictionary, sort_keys=True):
"""Return a dictionary formatted as a string using the expanded YAML format."""
import yaml

return yaml.dump(dictionary, sort_keys=sort_keys, default_flow_style=False)


Expand Down
6 changes: 4 additions & 2 deletions aiida/engine/daemon/execmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import pathlib
import shutil
from tempfile import NamedTemporaryFile
from typing import Any, List
from typing import Mapping as MappingType
from typing import Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, List

from aiida.common import AIIDA_LOGGER, exceptions
from aiida.common.datastructures import CalcInfo
Expand All @@ -35,7 +35,9 @@
from aiida.orm.utils.log import get_dblogger_extra
from aiida.repository.common import FileType
from aiida.schedulers.datastructures import JobState
from aiida.transports import Transport

if TYPE_CHECKING:
from aiida.transports import Transport

REMOTE_WORK_DIRECTORY_LOST_FOUND = 'lost+found'

Expand Down
4 changes: 2 additions & 2 deletions aiida/engine/processes/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
from typing import TYPE_CHECKING, Any, Type
from uuid import uuid4

import yaml

from aiida.engine.processes.ports import PortNamespace
from aiida.orm import Dict, Node
from aiida.orm.nodes.data.base import BaseType
Expand Down Expand Up @@ -245,6 +243,8 @@ def process_class(self) -> Type['Process']:

def _repr_pretty_(self, p, _) -> str: # pylint: disable=invalid-name
"""Pretty representation for in the IPython console and notebooks."""
import yaml

return p.text(
f'Process class: {self._process_class.__name__}\n'
f'Inputs:\n{yaml.safe_dump(json.JSONDecoder().decode(PrettyEncoder().encode(self)))}'
Expand Down
4 changes: 3 additions & 1 deletion aiida/engine/processes/calcjobs/monitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
from aiida.common.log import AIIDA_LOGGER
from aiida.orm import CalcJobNode, Dict
from aiida.plugins import BaseFactory
from aiida.transports import Transport

if t.TYPE_CHECKING:
from aiida.transports import Transport

LOGGER = AIIDA_LOGGER.getChild(__name__)

Expand Down
8 changes: 5 additions & 3 deletions aiida/engine/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import contextvars
import logging
import traceback
from typing import Awaitable, Dict, Hashable, Iterator, Optional
from typing import TYPE_CHECKING, Awaitable, Dict, Hashable, Iterator, Optional

from aiida.orm import AuthInfo
from aiida.transports import Transport

if TYPE_CHECKING:
from aiida.transports import Transport

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -55,7 +57,7 @@ def loop(self) -> asyncio.AbstractEventLoop:
return self._loop

@contextlib.contextmanager
def request_transport(self, authinfo: AuthInfo) -> Iterator[Awaitable[Transport]]:
def request_transport(self, authinfo: AuthInfo) -> Iterator[Awaitable['Transport']]:
"""
Request a transport from an authinfo. Because the client is not allowed to
request a transport immediately they will instead be given back a future
Expand Down
3 changes: 0 additions & 3 deletions aiida/manage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,10 @@
'CURRENT_CONFIG_VERSION',
'Config',
'ConfigValidationError',
'DEFAULT_DBINFO',
'MIGRATIONS',
'ManagementApiConnectionError',
'OLDEST_COMPATIBLE_CONFIG_VERSION',
'Option',
'Postgres',
'PostgresConnectionMode',
'Profile',
'RabbitmqManagementClient',
'check_and_migrate_config',
Expand Down
4 changes: 0 additions & 4 deletions aiida/manage/external/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,11 @@
# yapf: disable
# pylint: disable=wildcard-import

from .postgres import *
from .rmq import *

__all__ = (
'BROKER_DEFAULTS',
'DEFAULT_DBINFO',
'ManagementApiConnectionError',
'Postgres',
'PostgresConnectionMode',
'RabbitmqManagementClient',
'get_launch_queue_name',
'get_message_exchange_name',
Expand Down
2 changes: 0 additions & 2 deletions aiida/manage/external/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
if TYPE_CHECKING:
from aiida.manage.configuration import Profile

__all__ = ('Postgres', 'PostgresConnectionMode', 'DEFAULT_DBINFO')

# The last placeholder is for adding privileges of the user
_CREATE_USER_COMMAND = 'CREATE USER "{}" WITH PASSWORD \'{}\' {}'
_DROP_USER_COMMAND = 'DROP USER "{}"'
Expand Down
3 changes: 2 additions & 1 deletion aiida/manage/external/rmq/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import typing as t
from urllib.parse import quote

from aiida.common.exceptions import AiidaException

Expand Down Expand Up @@ -49,6 +48,8 @@ def format_url(self, url: str, url_params: dict[str, str] | None = None) -> str:
automatically inserted and should not be specified.
:returns: The complete URL.
"""
from urllib.parse import quote

url_params = url_params or {}
url_params['virtual_host'] = self._virtual_host if self._virtual_host else '/'
url_params = {key: quote(value, safe='') for key, value in url_params.items()}
Expand Down
3 changes: 2 additions & 1 deletion aiida/manage/external/rmq/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
"""Utilites for RabbitMQ."""
from urllib.parse import urlencode, urlunparse

from . import defaults

Expand All @@ -25,6 +24,8 @@ def get_rmq_url(protocol=None, username=None, password=None, host=None, port=Non
:param kwargs: remaining keyword arguments that will be encoded as query parameters.
:returns: the connection URL string.
"""
from urllib.parse import urlencode, urlunparse

if 'heartbeat' not in kwargs:
kwargs['heartbeat'] = defaults.BROKER_DEFAULTS.heartbeat

Expand Down
4 changes: 3 additions & 1 deletion aiida/schedulers/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
from aiida.common.lang import classproperty
from aiida.engine.processes.exit_code import ExitCode
from aiida.schedulers.datastructures import JobInfo, JobResource, JobTemplate, JobTemplateCodeInfo
from aiida.transports import Transport

if t.TYPE_CHECKING:
from aiida.transports import Transport

__all__ = ('Scheduler', 'SchedulerError', 'SchedulerParsingError')

Expand Down
1 change: 1 addition & 0 deletions docs/source/nitpick-exceptions
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ py:class callable
py:class function
py:class traceback
py:class NoneType
py:class MappingType
py:class AbstractContextManager
py:class BinaryIO
py:class IO
Expand Down

0 comments on commit 5dda6fd

Please sign in to comment.