|
4 | 4 |
|
5 | 5 | __author__ = 'DataCebo, Inc.' |
6 | 6 | |
7 | | -__version__ = '0.9.1' |
| 7 | +__version__ = '0.9.2.dev1' |
8 | 8 |
|
9 | 9 | import contextlib |
10 | 10 | import importlib |
| 11 | +import sys |
| 12 | +import warnings |
11 | 13 | from copy import deepcopy |
| 14 | +from operator import attrgetter |
12 | 15 |
|
13 | 16 | import numpy as np |
14 | 17 | import pandas as pd |
15 | | - |
16 | | -from copulas._addons import _find_addons |
17 | | - |
18 | | -_find_addons(group='copulas_modules', parent_globals=globals()) |
| 18 | +from pkg_resources import iter_entry_points |
19 | 19 |
|
20 | 20 | EPSILON = np.finfo(np.float32).eps |
21 | 21 |
|
@@ -262,3 +262,71 @@ def decorated(self, X, *args, **kwargs): |
262 | 262 | return function(self, X, *args, **kwargs) |
263 | 263 |
|
264 | 264 | return decorated |
| 265 | + |
| 266 | + |
| 267 | +def _get_addon_target(addon_path_name): |
| 268 | + """Find the target object for the add-on. |
| 269 | +
|
| 270 | + Args: |
| 271 | + addon_path_name (str): |
| 272 | + The add-on's name. The add-on's name should be the full path of valid Python |
| 273 | + identifiers (i.e. importable.module:object.attr). |
| 274 | +
|
| 275 | + Returns: |
| 276 | + tuple: |
| 277 | + * object: |
| 278 | + The base module or object the add-on should be added to. |
| 279 | + * str: |
| 280 | + The name the add-on should be added to under the module or object. |
| 281 | + """ |
| 282 | + module_path, _, object_path = addon_path_name.partition(':') |
| 283 | + module_path = module_path.split('.') |
| 284 | + |
| 285 | + if module_path[0] != __name__: |
| 286 | + msg = f"expected base module to be '{__name__}', found '{module_path[0]}'" |
| 287 | + raise AttributeError(msg) |
| 288 | + |
| 289 | + target_base = sys.modules[__name__] |
| 290 | + for submodule in module_path[1:-1]: |
| 291 | + target_base = getattr(target_base, submodule) |
| 292 | + |
| 293 | + addon_name = module_path[-1] |
| 294 | + if object_path: |
| 295 | + if len(module_path) > 1 and not hasattr(target_base, module_path[-1]): |
| 296 | + msg = f"cannot add '{object_path}' to unknown submodule '{'.'.join(module_path)}'" |
| 297 | + raise AttributeError(msg) |
| 298 | + |
| 299 | + if len(module_path) > 1: |
| 300 | + target_base = getattr(target_base, module_path[-1]) |
| 301 | + |
| 302 | + split_object = object_path.split('.') |
| 303 | + addon_name = split_object[-1] |
| 304 | + |
| 305 | + if len(split_object) > 1: |
| 306 | + target_base = attrgetter('.'.join(split_object[:-1]))(target_base) |
| 307 | + |
| 308 | + return target_base, addon_name |
| 309 | + |
| 310 | + |
| 311 | +def _find_addons(): |
| 312 | + """Find and load all copulas add-ons.""" |
| 313 | + group = 'copulas_modules' |
| 314 | + for entry_point in iter_entry_points(group=group): |
| 315 | + try: |
| 316 | + addon = entry_point.load() |
| 317 | + except Exception: # pylint: disable=broad-exception-caught |
| 318 | + msg = f'Failed to load "{entry_point.name}" from "{entry_point.module_name}".' |
| 319 | + warnings.warn(msg) |
| 320 | + continue |
| 321 | + |
| 322 | + try: |
| 323 | + addon_target, addon_name = _get_addon_target(entry_point.name) |
| 324 | + except AttributeError as error: |
| 325 | + msg = f"Failed to set '{entry_point.name}': {error}." |
| 326 | + warnings.warn(msg) |
| 327 | + continue |
| 328 | + |
| 329 | + setattr(addon_target, addon_name, addon) |
| 330 | + |
| 331 | + |
| 332 | +_find_addons() |
0 commit comments