Skip to content
Merged
10 changes: 10 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
Version NEXTVERSION
-------------------

**2025-12-??**

* Reduce the time taken to import `cfdm`
(https://github.com/NCAS-CMS/cfdm/issues/361)

----

Version 1.12.3.1
----------------

Expand Down
162 changes: 0 additions & 162 deletions cfdm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,174 +40,12 @@
import logging
import sys

from packaging.version import Version

from . import core

__date__ = core.__date__
__cf_version__ = core.__cf_version__
__version__ = core.__version__

_requires = core._requires + (
Copy link
Member

Choose a reason for hiding this comment

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

Do you mean to get rid of all of this version checking? We can still do it without having to actually import said modules, with use of importlib.metadata to see what is available in the existing environment, e.g:

>>> from importlib import metadata
>>> metadata.version("cftime")
'1.6.4'
>>> metadata.version("netCDF4")
'1.7.2'
>>> metadata.version("dask")
'2025.7.0'

(Good old metadata coming in useful!) It would be a shame to lose the useful checks in the name of import speed...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah - I didn't know about this, thanks. I'll try it out and make a new commit if all goes well (not today!). Playing on the command line, each metadata.version call takes ~0.5 milliseconds - so not too expensive :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

... Having chatted about this offline, we decided to leave things as they are here for now, but open another issue to look into what we want to do/don't in this area (which I will do soon).

"cftime",
"netCDF4",
"dask",
"scipy",
"h5netcdf",
"zarr",
"s3fs",
"uritools",
"cfunits",
)

_error0 = f"cfdm requires the modules {', '.join(_requires)}. "

# Check the version of cftime
try:
import cftime
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "1.6.4"
if Version(cftime.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad cftime version: cfdm requires cftime>={_minimum_vn}. "
f"Got {cftime.__version__} at {cftime.__file__}"
)

# Check the version of netCDF4
try:
import netCDF4
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "1.7.2"
if Version(netCDF4.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad netCDF4 version: cfdm requires netCDF4>={_minimum_vn}. "
f"Got {netCDF4.__version__} at {netCDF4.__file__}"
)

# Check the version of h5netcdf
try:
import h5netcdf
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "1.3.0"
if Version(h5netcdf.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad h5netcdf version: cfdm requires h5netcdf>={_minimum_vn}. "
f"Got {h5netcdf.__version__} at {h5netcdf.__file__}"
)

# Check the version of h5py
try:
import h5py
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "3.12.0"
if Version(h5py.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad h5py version: cfdm requires h5py>={_minimum_vn}. "
f"Got {h5py.__version__} at {h5py.__file__}"
)

# Check the version of zarr
try:
import zarr
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "3.0.8"
if Version(zarr.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad zarr version: cfdm requires zarr>={_minimum_vn}. "
f"Got {zarr.__version__} at {zarr.__file__}"
)

# Check the version of s3fs
try:
import s3fs
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "2024.6.0"
if Version(s3fs.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad s3fs version: cfdm requires s3fs>={_minimum_vn}. "
f"Got {s3fs.__version__} at {s3fs.__file__}"
)

# Check the version of scipy
try:
import scipy
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "1.10.0"
if Version(scipy.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad scipy version: cfdm requires scipy>={_minimum_vn}. "
f"Got {scipy.__version__} at {scipy.__file__}"
)

# Check the version of dask
try:
import dask
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "2025.5.1"
if Version(dask.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad dask version: cfdm requires dask>={_minimum_vn}. "
f"Got {dask.__version__} at {dask.__file__}"
)

# Check the version of distributed
try:
import distributed
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "2025.5.1"
if Version(distributed.__version__) < Version(_minimum_vn):
raise ValueError(
"Bad distributed version: cfdm requires "
f"distributed>={_minimum_vn}. "
f"Got {distributed.__version__} at {distributed.__file__}"
)

# Check the version of uritools
try:
import uritools
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "4.0.3"
if Version(uritools.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad uritools version: cfdm requires uritools>={_minimum_vn}. "
f"Got {uritools.__version__} at {uritools.__file__}"
)

# Check the version of cfunits
try:
import cfunits
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "3.3.7"
if Version(cfunits.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad cfunits version: cfdm requires cfunits>={_minimum_vn}. "
f"Got {cfunits.__version__} at {cfunits.__file__}"
)

del _minimum_vn

from .constants import masked

# Internal ones passed on so they can be used in cf-python (see
Expand Down
36 changes: 0 additions & 36 deletions cfdm/constants.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,6 @@
import logging
import sys
from enum import Enum

import numpy as np
from dask import config
from dask.utils import parse_bytes

_CHUNKSIZE = "128 MiB"
config.set({"array.chunk-size": _CHUNKSIZE})
"""A dictionary of useful constants.

Whilst the dictionary may be modified directly, it is safer to
retrieve and set the values with the dedicated get-and-set functions.

:Keys:

ATOL: `float`
The value of absolute tolerance for testing numerically tolerant
equality.

RTOL: `float`
The value of relative tolerance for testing numerically tolerant
equality.

LOG_LEVEL: `str`
The minimal level of seriousness for which log messages are
shown. See `cfdm.log_level`.

CHUNKSIZE: `int`
The Dask chunk size (in bytes). See `cfdm.chunksize`.

"""
CONSTANTS = {
"ATOL": sys.float_info.epsilon,
"RTOL": sys.float_info.epsilon,
"LOG_LEVEL": logging.getLevelName(logging.getLogger().level),
"CHUNKSIZE": parse_bytes(_CHUNKSIZE),
}


# --------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions cfdm/constructs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from itertools import zip_longest
from re import Pattern

from . import core, mixin
from .core import Constructs as core_Constructs
from .core.functions import deepcopy
from .decorators import _manage_log_level_via_verbosity
from .mixin import Container

logger = logging.getLogger(__name__)


class Constructs(mixin.Container, core.Constructs):
class Constructs(Container, core_Constructs):
"""A container for metadata constructs.

The container has similarities to a `dict` in that it presents the
Expand Down
46 changes: 3 additions & 43 deletions cfdm/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,12 @@
__cf_version__ = "1.12"
__version__ = "1.12.3.1"

from platform import python_version

_requires = ("numpy", "packaging")
_error0 = f"cfdm.core requires the modules {', '.join(_requires)}. "

# Check the version of packaging
try:
import packaging
from packaging.version import Version
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "20.0"
if Version(packaging.__version__) < Version(_minimum_vn):
raise RuntimeError(
f"Bad packaging version: cf requires packaging>={_minimum_vn}. "
f"Got {packaging.__version__} at {packaging.__file__}"
)

# Check the version of python
_minimum_vn = "3.10.0"
if Version(python_version()) < Version(_minimum_vn):
raise ValueError(
f"Bad python version: cfdm.core requires python>={_minimum_vn}. "
f"Got {python_version()}"
)

# Check the version of numpy
try:
import numpy as np
except ImportError as error1:
raise ImportError(_error0 + str(error1))
else:
_minimum_vn = "2.0.0"
if Version(np.__version__) < Version(_minimum_vn):
raise ValueError(
f"Bad numpy version: cfdm.core requires numpy>={_minimum_vn}. "
f"Got {np.__version__} at {np.__file__}"
)

del _minimum_vn
# Count the number of docstrings (first element), and the number which
# have docstring substitutions applied to them (second element).
_docstring_substitutions = [0, 0]

from .constructs import Constructs

from .functions import CF, environment

from .data import Data, Array, NumpyArray

from .bounds import Bounds
Expand Down
6 changes: 3 additions & 3 deletions cfdm/core/cellconnectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def construct_type(self):
def del_connectivity(self, default=ValueError()):
"""Remove the connectivity.

{{{cell connectivity type}}
{{cell connectivity type}}

.. versionadded:: (cfdm) 1.11.0.0

Expand Down Expand Up @@ -149,7 +149,7 @@ def del_connectivity(self, default=ValueError()):
def has_connectivity(self):
"""Whether the connectivity type has been set.

{{{cell connectivity type}}
{{cell connectivity type}}

.. versionadded:: (cfdm) 1.11.0.0

Expand Down Expand Up @@ -233,7 +233,7 @@ def get_connectivity(self, default=ValueError()):
def set_connectivity(self, connectivity):
"""Set the connectivity type.

{{{cell connectivity type}}
{{cell connectivity type}}

.. versionadded:: (cfdm) 1.11.0.0

Expand Down
4 changes: 2 additions & 2 deletions cfdm/core/data/abstract/array.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from functools import reduce
from operator import mul

from ...abstract import Container
from ...utils import cached_property
from cfdm.core.abstract import Container
from cfdm.core.utils import cached_property


class Array(Container):
Expand Down
3 changes: 2 additions & 1 deletion cfdm/core/data/data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np

from .. import abstract
from cfdm.core import abstract

from .abstract import Array
from .numpyarray import NumpyArray

Expand Down
13 changes: 5 additions & 8 deletions cfdm/core/docstring/docstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@

Replacement text may not contain other non-special substitutions.

Keys must be a `str` or `re.Pattern` object:

* If a key is a `str` then the corresponding value must be a string.

* If a key is a `re.Pattern` object then the corresponding value must
be a string or a callable, as accepted by the `re.Pattern.sub`
method.
A key and its corresponding value must both be `str`.

.. versionaddedd:: (cfdm) 1.8.7.0

Expand Down Expand Up @@ -95,7 +89,10 @@
"{{init data: data_like, optional}}": """data: data_like, optional
Set the data.

{{data_like}}
A data_like object is any object that can be converted
to a `Data` object, i.e. `numpy` array_like objects,
`Data` objects, and {{package}} instances that contain
`Data` objects.

The data also may be set after initialisation with the
`set_data` method.""",
Expand Down
Loading