Source code for negmas.helpers.types

#!/usr/bin/env python
"""
Datatypes that do not directly relate to negotiation.
"""

from __future__ import annotations

import functools
import importlib
import json
from enum import Enum
from os import PathLike
from types import FunctionType, LambdaType
from typing import Any, Callable, overload

import stringcase

__all__ = [
    "PathLike",
    "TYPE_START",
    "ReturnCause",
    "get_class",
    "import_by_name",
    "get_full_type_name",
    "instantiate",
    "is_jsonable",
    "is_lambda_function",
    "is_partial_function",
    "is_lambda_or_partial_function",
    "is_type",
    "is_not_lambda_nor_partial_function",
]

TYPE_START = "__TYPE__:"


[docs] class ReturnCause(Enum): TIMEOUT = 0 SUCCESS = 1 FAILURE = 2
@overload def get_full_type_name(t: None) -> None: ... @overload def get_full_type_name(t: str) -> str: ... @overload def get_full_type_name(t: type[Any] | Callable) -> str: ...
[docs] def get_full_type_name(t: type[Any] | Callable | str | None) -> str | None: """ Gets the full type name of a type. You *should not* pass an instance to this function but it may just work. An exception is that if the input is of type `str` or if it is None, it will be returned as it is """ if t is None or isinstance(t, str): return t if isinstance(t, functools.partial): t = t.func if not hasattr(t, "__module__") and not hasattr(t, "__name__"): t = type(t) # if t.__module__ in ("__main__", "__mp_main__"): # return t.__name__ return t.__module__ + "." + t.__name__ # type: ignore
[docs] def import_by_name(full_name: str) -> Any: """Imports something form a module using its full name""" if not isinstance(full_name, str): return full_name modules: list[str] = [] parts = full_name.split(".") modules = parts[:-1] module_name = ".".join(modules) item_name = parts[-1] if len(modules) < 1: raise ValueError( f"Cannot get the object {item_name} in module {module_name} (modules {modules})" ) module = importlib.import_module(module_name) return getattr(module, item_name)
[docs] def get_class( class_name: str | type, module_name: str | None = None, scope: dict | None = None, allow_nonstandard_names=False, ) -> type: """Imports and creates a class object for the given class name""" if not isinstance(class_name, str): return class_name # If the class is in the main module just load it directly # if "." not in class_name: # for module_name in ("__main__", "__mp_main__"): # try: # module = importlib.import_module(module_name) # except Exception: # module = None # if module: # try: # t = getattr(module, class_name) # if t: # return t # except Exception: # pass # else: # try: # return eval(class_name) # except Exception: # raise ValueError(f"Cannot get the class {class_name} in main module") # remove explicit type annotation in the string. Used when serializing while class_name.startswith(TYPE_START): class_name = class_name[len(TYPE_START) :] modules: list[str] = [] if module_name is not None: modules = module_name.split(".") modules += class_name.split(".") if len(modules) < 1: raise ValueError( f"Cannot get the class {class_name} in module {module_name} (modules {modules})" ) if not class_name.startswith("builtins") or allow_nonstandard_names: class_name = stringcase.pascalcase(modules[-1]) else: class_name = modules[-1] if len(modules) < 2: return eval(class_name, scope) module_name = ".".join(modules[:-1]) module = importlib.import_module(module_name) return getattr(module, class_name)
[docs] def instantiate( class_name: str | type, module_name: str | None = None, scope: dict | None = None, **kwargs, ) -> Any: """Imports and instantiates an object of a class""" return get_class(class_name, module_name)(**kwargs)
[docs] def is_lambda_function(obj): """Checks if the given object is a lambda function""" return isinstance(obj, LambdaType) and obj.__name__ == "<lambda>"
[docs] def is_partial_function(obj): """Checks if the given object is a lambda function""" return isinstance(obj, functools.partial)
[docs] def is_lambda_or_partial_function(obj): """Checks if the given object is a lambda function or a partial function""" return is_lambda_function(obj) or is_partial_function(obj)
[docs] def is_type(obj): """Checks if the given object is a type converted to string""" return isinstance(obj, type)
def is_not_type(obj): """Checks if the given object is not a type converted to string""" return not is_type(obj)
[docs] def is_not_lambda_nor_partial_function(obj): """Checks if the given object is not a lambda function""" return isinstance(obj, FunctionType) and ( obj.__name__ != "<lambda>" and not isinstance(obj, functools.partial) )
[docs] def is_jsonable(x): try: json.dumps(x) return True except Exception: return False
# class Proxy: # """A general proxy class.""" # # def __init__(self, obj): # self._obj = obj # # def __getattr__(self, item): # return getattr(self._obj, item) # class LazyInitializable: # """ # Not used # # Supports a set_params function that can be used for lazy initialization # """ # # def __init__(self) -> None: # super().__init__() # # def set_params(self, **kwargs) -> None: # """Sets the attributes of the object. # # This function can be used to set the attributes of any object to the # same values used in its construction which allows for lazy # initialization. # # Args: # **kwargs: The parameters usually passed to the constructor as a dict # # Example: # # >>> class A(LazyInitializable): # ... def __init__(self, a=None, b=None) -> None: # ... super().__init__() # ... self.a = a # ... self.b = b # # Now you can do the following:: # # >>> a = A() # >>> a.set_params(a=3, b=2) # # which will be equivalent to: # # >>> b = A(a=3, b=2) # # Remarks: # - See ``adjust_params()`` for an example in which the constuctor needs to do more processing than just # assinging its inputs to instance members. # # """ # for k, v in kwargs.items(): # setattr(self, k, v) # self.adjust_params() # # def adjust_params(self) -> None: # """ # Adjust the internal attributes following ``set_attributes()`` or construction using ``__init__()``. # # This function needs to be implemented only if the constructor needs to # do some processing on the inputs other than assigning it to instance # attributes. In such case, move these adjustments to this function and # call it in the constructor. # # Examples: # # >>> class A(object): # ... def __init__(self, a=None, b=None): # ... self.a = a # ... self.b = b if b is not None else [] # # should now be defined as follows: # # >>> class A(LazyInitializable): # ... def __init__(self, a, b): # ... super().__init__() # ... self.a = a # ... self.b = b # ... self.adjust_params() # ... # ... def adjust_params(self): # ... if self.b is None: self.b = [] # # Remarks: # - Remember to call `super().__init__()` first in your constructor and to call your `adjust_params()` by # the end of the constructor. # - The constructor should ONLY copy the parameters it receives to internal variables and then calls # `adjust_params()` if any more processing is needed. This makes it possible to use `set_params()` with # this object. # - You should **never** call `adjust_params()` directly anywhere. # """