chore: import upstream snapshot with attribution
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This file is taken from https://github.com/NVIDIA-NeMo/Curator/blob/dask/nemo_curator/utils/import_utils.py,
|
||||
# which is adapted from cuML's safe_imports module:
|
||||
# https://github.com/rapidsai/cuml/blob/e93166ea0dddfa8ef2f68c6335012af4420bc8ac/python/cuml/internals/safe_imports.py
|
||||
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
|
||||
GPU_INSTALL_STRING = (
|
||||
"""Install GPU packages via `pip install --extra-index-url """
|
||||
"""https://pypi.nvidia.com nemo-curator[cuda12x]`
|
||||
or use `pip install --extra-index-url https://pypi.nvidia.com ".[cuda12x]"` if installing from source"""
|
||||
)
|
||||
|
||||
|
||||
class UnavailableError(Exception):
|
||||
"""Error thrown if a symbol is unavailable due to an issue importing it"""
|
||||
|
||||
|
||||
@contextmanager
|
||||
def null_decorator(*args, **kwargs):
|
||||
"""null_decorator"""
|
||||
if len(kwargs) == 0 and len(args) == 1 and callable(args[0]):
|
||||
return args[0]
|
||||
else:
|
||||
|
||||
def inner(func):
|
||||
return func
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class UnavailableMeta(type):
|
||||
"""A metaclass for generating placeholder objects for unavailable symbols
|
||||
|
||||
This metaclass allows errors to be deferred from import time to the time
|
||||
that a symbol is actually used in order to streamline the usage of optional
|
||||
dependencies. This is particularly useful for attempted imports of GPU-only
|
||||
modules which will only be invoked if GPU-only functionality is
|
||||
specifically used.
|
||||
|
||||
If an attempt to import a symbol fails, this metaclass is used to generate
|
||||
a class which stands in for that symbol. Any attempt to call the symbol
|
||||
(instantiate the class) or access its attributes will throw an
|
||||
UnavailableError exception. Furthermore, this class can be used in
|
||||
e.g. isinstance checks, since it will (correctly) fail to match any
|
||||
instance it is compared against.
|
||||
|
||||
In addition to calls and attribute access, a number of dunder methods are
|
||||
implemented so that other common usages of imported symbols (e.g.
|
||||
arithmetic) throw an UnavailableError, but this is not guaranteed for
|
||||
all possible uses. In such cases, other exception types (typically
|
||||
TypeErrors) will be thrown instead.
|
||||
"""
|
||||
|
||||
def __new__(meta, name, bases, dct):
|
||||
if dct.get("_msg", None) is None:
|
||||
dct["_msg"] = f"{name} could not be imported"
|
||||
name = f"MISSING{name}"
|
||||
return super(UnavailableMeta, meta).__new__(meta, name, bases, dct)
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __getattr__(cls, name):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __eq__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __lt__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __gt__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __le__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __ge__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __ne__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __abs__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __add__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __radd__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __iadd__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __floordiv__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rfloordiv__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __ifloordiv__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __lshift__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rlshift__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __mul__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rmul__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __imul__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __ilshift__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __pow__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rpow__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __ipow__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rshift__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rrshift__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __irshift__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __sub__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rsub__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __isub__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __truediv__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rtruediv__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __itruediv__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __divmod__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __rdivmod__(cls, other):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __neg__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __invert__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __hash__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __index__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __iter__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __delitem__(cls, name):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __setitem__(cls, name, value):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __enter__(cls, *args, **kwargs):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __get__(cls, *args, **kwargs):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __delete__(cls, *args, **kwargs):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
def __len__(cls):
|
||||
raise UnavailableError(cls._msg)
|
||||
|
||||
|
||||
def is_unavailable(obj):
|
||||
"""Helper to check if given symbol is actually a placeholder"""
|
||||
return type(obj) is UnavailableMeta
|
||||
|
||||
|
||||
class UnavailableNullContext:
|
||||
"""A placeholder class for unavailable context managers
|
||||
|
||||
This context manager will return a value which will throw an
|
||||
UnavailableError if used in any way, but the context manager itself can be
|
||||
safely invoked.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return UnavailableMeta(
|
||||
"MissingContextValue",
|
||||
(),
|
||||
{"_msg": "Attempted to make use of placeholder context return value."},
|
||||
)
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def safe_import(module, *, msg=None, alt=None):
|
||||
"""A function used to import modules that may not be available
|
||||
|
||||
This function will attempt to import a module with the given name, but it
|
||||
will not throw an ImportError if the module is not found. Instead, it will
|
||||
return a placeholder object which will raise an exception only if used.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: str
|
||||
The name of the module to import.
|
||||
msg: str or None
|
||||
An optional error message to be displayed if this module is used
|
||||
after a failed import.
|
||||
alt: object
|
||||
An optional module to be used in place of the given module if it
|
||||
fails to import
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple(object, bool)
|
||||
The imported module, the given alternate, or a class derived from
|
||||
UnavailableMeta, and a boolean indicating whether the intended import was successful.
|
||||
"""
|
||||
try:
|
||||
return importlib.import_module(module), True
|
||||
except ImportError:
|
||||
exception_text = traceback.format_exc()
|
||||
logger.debug(f"Import of {module} failed with: {exception_text}")
|
||||
except Exception:
|
||||
exception_text = traceback.format_exc()
|
||||
raise
|
||||
if msg is None:
|
||||
msg = f"{module} could not be imported"
|
||||
if alt is None:
|
||||
return UnavailableMeta(module.rsplit(".")[-1], (), {"_msg": msg}), False
|
||||
else:
|
||||
return alt, False
|
||||
|
||||
|
||||
def safe_import_from(module, symbol, *, msg=None, alt=None, fallback_module=None):
|
||||
"""A function used to import symbols from modules that may not be available
|
||||
|
||||
This function will attempt to import a symbol with the given name from
|
||||
the given module, but it will not throw an ImportError if the symbol is not
|
||||
found. Instead, it will return a placeholder object which will raise an
|
||||
exception only if used.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: str
|
||||
The name of the module in which the symbol is defined.
|
||||
symbol: str
|
||||
The name of the symbol to import.
|
||||
msg: str or None
|
||||
An optional error message to be displayed if this symbol is used
|
||||
after a failed import.
|
||||
alt: object
|
||||
An optional object to be used in place of the given symbol if it fails
|
||||
to import
|
||||
fallback_module: str
|
||||
Alternative name of the model in which the symbol is defined. The function will first to
|
||||
import using the `module` value and if that fails will also try the `fallback_module`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple(object, bool)
|
||||
The imported symbol, the given alternate, or a class derived from
|
||||
UnavailableMeta, and a boolean indicating whether the intended import was successful.
|
||||
"""
|
||||
try:
|
||||
imported_module = importlib.import_module(module)
|
||||
return getattr(imported_module, symbol), True
|
||||
except ImportError:
|
||||
exception_text = traceback.format_exc()
|
||||
logger.debug(f"Import of {module} failed with: {exception_text}")
|
||||
except AttributeError:
|
||||
# if there is a fallback module try it.
|
||||
if fallback_module is not None:
|
||||
return safe_import_from(fallback_module, symbol, msg=msg, alt=alt, fallback_module=None)
|
||||
exception_text = traceback.format_exc()
|
||||
logger.info(f"Import of {symbol} from {module} failed with: {exception_text}")
|
||||
except Exception:
|
||||
exception_text = traceback.format_exc()
|
||||
raise
|
||||
if msg is None:
|
||||
msg = f"{module}.{symbol} could not be imported"
|
||||
if alt is None:
|
||||
return UnavailableMeta(symbol, (), {"_msg": msg}), False
|
||||
else:
|
||||
return alt, False
|
||||
|
||||
|
||||
def gpu_only_import(module, *, alt=None):
|
||||
"""A function used to import modules required only in GPU installs
|
||||
|
||||
This function will attempt to import a module with the given name.
|
||||
This function will attempt to import a symbol with the given name from
|
||||
the given module, but it will not throw an ImportError if the symbol is not
|
||||
found. Instead, it will return a placeholder object which will raise an
|
||||
exception only if used with instructions on installing a GPU build.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: str
|
||||
The name of the module to import.
|
||||
alt: object
|
||||
An optional module to be used in place of the given module if it
|
||||
fails to import in a non-GPU-enabled install
|
||||
|
||||
Returns
|
||||
-------
|
||||
object
|
||||
The imported module, the given alternate, or a class derived from
|
||||
UnavailableMeta.
|
||||
"""
|
||||
|
||||
return safe_import(
|
||||
module,
|
||||
msg=f"{module} is not enabled in non GPU-enabled installations or environemnts. {GPU_INSTALL_STRING}",
|
||||
alt=alt,
|
||||
)
|
||||
|
||||
|
||||
def gpu_only_import_from(module, symbol, *, alt=None):
|
||||
"""A function used to import symbols required only in GPU installs
|
||||
|
||||
This function will attempt to import a module with the given name.
|
||||
This function will attempt to import a symbol with the given name from
|
||||
the given module, but it will not throw an ImportError if the symbol is not
|
||||
found. Instead, it will return a placeholder object which will raise an
|
||||
exception only if used with instructions on installing a GPU build.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: str
|
||||
The name of the module to import.
|
||||
symbol: str
|
||||
The name of the symbol to import.
|
||||
alt: object
|
||||
An optional object to be used in place of the given symbol if it fails
|
||||
to import in a non-GPU-enabled install
|
||||
|
||||
Returns
|
||||
-------
|
||||
object
|
||||
The imported symbol, the given alternate, or a class derived from
|
||||
UnavailableMeta.
|
||||
"""
|
||||
return safe_import_from(
|
||||
module,
|
||||
symbol,
|
||||
msg=f"{module}.{symbol} is not enabled in non GPU-enabled installations or environments. {GPU_INSTALL_STRING}",
|
||||
alt=alt,
|
||||
)
|
||||
Reference in New Issue
Block a user