-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding pandas, scipy interpolate, openpyxl and pint to lazy import in…
… readers (though pint is used in core, so it will be loaded on initial cellpy import)
- Loading branch information
Showing
14 changed files
with
559 additions
and
236 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
""" | ||
apipkg: control the exported namespace of a Python package. | ||
see https://pypi.python.org/pypi/apipkg | ||
(c) holger krekel, 2009 - MIT license | ||
""" | ||
from __future__ import annotations | ||
|
||
__all__ = ["initpkg", "ApiModule", "AliasModule", "__version__", "distribution_version"] | ||
import sys | ||
from typing import Any | ||
|
||
from ._alias_module import AliasModule | ||
from ._importing import distribution_version as distribution_version | ||
from ._module import _initpkg | ||
from ._module import ApiModule | ||
from ._version import version as __version__ | ||
|
||
|
||
def initpkg( | ||
pkgname: str, | ||
exportdefs: dict[str, Any], | ||
attr: dict[str, object] | None = None, | ||
eager: bool = False, | ||
) -> ApiModule: | ||
"""initialize given package from the export definitions.""" | ||
attr = attr or {} | ||
mod = sys.modules.get(pkgname) | ||
|
||
mod = _initpkg(mod, pkgname, exportdefs, attr=attr) | ||
|
||
# eagerload in bypthon to avoid their monkeypatching breaking packages | ||
if "bpython" in sys.modules or eager: | ||
for module in list(sys.modules.values()): | ||
if isinstance(module, ApiModule): | ||
getattr(module, "__dict__") | ||
|
||
return mod |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from __future__ import annotations | ||
|
||
from types import ModuleType | ||
|
||
from ._importing import importobj | ||
|
||
|
||
def AliasModule(modname: str, modpath: str, attrname: str | None = None) -> ModuleType: | ||
cached_obj: object | None = None | ||
|
||
def getmod() -> object: | ||
nonlocal cached_obj | ||
if cached_obj is None: | ||
cached_obj = importobj(modpath, attrname) | ||
return cached_obj | ||
|
||
x = modpath + ("." + attrname if attrname else "") | ||
repr_result = f"<AliasModule {modname!r} for {x!r}>" | ||
|
||
class AliasModule(ModuleType): | ||
def __repr__(self) -> str: | ||
return repr_result | ||
|
||
def __getattribute__(self, name: str) -> object: | ||
try: | ||
return getattr(getmod(), name) | ||
except ImportError: | ||
if modpath == "pytest" and attrname is None: | ||
# hack for pylibs py.test | ||
return None | ||
else: | ||
raise | ||
|
||
def __setattr__(self, name: str, value: object) -> None: | ||
setattr(getmod(), name, value) | ||
|
||
def __delattr__(self, name: str) -> None: | ||
delattr(getmod(), name) | ||
|
||
return AliasModule(str(modname)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
import sys | ||
|
||
|
||
def _py_abspath(path: str) -> str: | ||
""" | ||
special version of abspath | ||
that will leave paths from jython jars alone | ||
""" | ||
if path.startswith("__pyclasspath__"): | ||
return path | ||
else: | ||
return os.path.abspath(path) | ||
|
||
|
||
def distribution_version(name: str) -> str | None: | ||
"""try to get the version of the named distribution, | ||
returns None on failure""" | ||
if sys.version_info >= (3, 8): | ||
from importlib.metadata import PackageNotFoundError, version | ||
else: | ||
from importlib_metadata import PackageNotFoundError, version | ||
try: | ||
return version(name) | ||
except PackageNotFoundError: | ||
return None | ||
|
||
|
||
def importobj(modpath: str, attrname: str | None) -> object: | ||
"""imports a module, then resolves the attrname on it""" | ||
module = __import__(modpath, None, None, ["__doc__"]) | ||
if not attrname: | ||
return module | ||
|
||
retval = module | ||
names = attrname.split(".") | ||
for x in names: | ||
retval = getattr(retval, x) | ||
return retval |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
from __future__ import annotations | ||
|
||
import sys | ||
import threading | ||
from types import ModuleType | ||
from typing import Any | ||
from typing import Callable | ||
from typing import cast | ||
from typing import Iterable | ||
|
||
from ._alias_module import AliasModule | ||
from ._importing import _py_abspath | ||
from ._importing import importobj | ||
from ._syncronized import _synchronized | ||
|
||
|
||
class ApiModule(ModuleType): | ||
"""the magical lazy-loading module standing""" | ||
|
||
def __docget(self) -> str | None: | ||
try: | ||
return self.__doc | ||
except AttributeError: | ||
if "__doc__" in self.__map__: | ||
return cast(str, self.__makeattr("__doc__")) | ||
else: | ||
return None | ||
|
||
def __docset(self, value: str) -> None: | ||
self.__doc = value | ||
|
||
__doc__ = property(__docget, __docset) # type: ignore | ||
__map__: dict[str, tuple[str, str]] | ||
|
||
def __init__( | ||
self, | ||
name: str, | ||
importspec: dict[str, Any], | ||
implprefix: str | None = None, | ||
attr: dict[str, Any] | None = None, | ||
) -> None: | ||
super().__init__(name) | ||
self.__name__ = name | ||
self.__all__ = [x for x in importspec if x != "__onfirstaccess__"] | ||
self.__map__ = {} | ||
self.__implprefix__ = implprefix or name | ||
if attr: | ||
for name, val in attr.items(): | ||
setattr(self, name, val) | ||
for name, importspec in importspec.items(): | ||
if isinstance(importspec, dict): | ||
subname = f"{self.__name__}.{name}" | ||
apimod = ApiModule(subname, importspec, implprefix) | ||
sys.modules[subname] = apimod | ||
setattr(self, name, apimod) | ||
else: | ||
parts = importspec.split(":") | ||
modpath = parts.pop(0) | ||
attrname = parts and parts[0] or "" | ||
if modpath[0] == ".": | ||
modpath = implprefix + modpath | ||
|
||
if not attrname: | ||
subname = f"{self.__name__}.{name}" | ||
apimod = AliasModule(subname, modpath) | ||
sys.modules[subname] = apimod | ||
if "." not in name: | ||
setattr(self, name, apimod) | ||
else: | ||
self.__map__[name] = (modpath, attrname) | ||
|
||
def __repr__(self): | ||
repr_list = [f"<ApiModule {self.__name__!r}"] | ||
if hasattr(self, "__version__"): | ||
repr_list.append(f" version={self.__version__!r}") | ||
if hasattr(self, "__file__"): | ||
repr_list.append(f" from {self.__file__!r}") | ||
repr_list.append(">") | ||
return "".join(repr_list) | ||
|
||
@_synchronized | ||
def __makeattr(self, name, isgetattr=False): | ||
"""lazily compute value for name or raise AttributeError if unknown.""" | ||
target = None | ||
if "__onfirstaccess__" in self.__map__: | ||
target = self.__map__.pop("__onfirstaccess__") | ||
fn = cast(Callable[[], None], importobj(*target)) | ||
fn() | ||
try: | ||
modpath, attrname = self.__map__[name] | ||
except KeyError: | ||
# __getattr__ is called when the attribute does not exist, but it may have | ||
# been set by the onfirstaccess call above. Infinite recursion is not | ||
# possible as __onfirstaccess__ is removed before the call (unless the call | ||
# adds __onfirstaccess__ to __map__ explicitly, which is not our problem) | ||
if target is not None and name != "__onfirstaccess__": | ||
return getattr(self, name) | ||
# Attribute may also have been set during a concurrent call to __getattr__ | ||
# which executed after this call was already waiting on the lock. Check | ||
# for a recently set attribute while avoiding infinite recursion: | ||
# * Don't call __getattribute__ if __makeattr was called from a data | ||
# descriptor such as the __doc__ or __dict__ properties, since data | ||
# descriptors are called as part of object.__getattribute__ | ||
# * Only call __getattribute__ if there is a possibility something has set | ||
# the attribute we're looking for since __getattr__ was called | ||
if threading is not None and isgetattr: | ||
return super().__getattribute__(name) | ||
raise AttributeError(name) | ||
else: | ||
result = importobj(modpath, attrname) | ||
setattr(self, name, result) | ||
# in a recursive-import situation a double-del can happen | ||
self.__map__.pop(name, None) | ||
return result | ||
|
||
def __getattr__(self, name): | ||
return self.__makeattr(name, isgetattr=True) | ||
|
||
def __dir__(self) -> Iterable[str]: | ||
yield from super().__dir__() | ||
yield from self.__map__ | ||
|
||
@property | ||
def __dict__(self) -> dict[str, Any]: # type: ignore | ||
# force all the content of the module | ||
# to be loaded when __dict__ is read | ||
dictdescr = ModuleType.__dict__["__dict__"] # type: ignore | ||
ns: dict[str, Any] = dictdescr.__get__(self) | ||
if ns is not None: | ||
hasattr(self, "some") | ||
for name in self.__all__: | ||
try: | ||
self.__makeattr(name) | ||
except AttributeError: | ||
pass | ||
return ns | ||
|
||
|
||
_PRESERVED_MODULE_ATTRS = { | ||
"__file__", | ||
"__version__", | ||
"__loader__", | ||
"__path__", | ||
"__package__", | ||
"__doc__", | ||
"__spec__", | ||
"__dict__", | ||
} | ||
|
||
|
||
def _initpkg(mod: ModuleType | None, pkgname, exportdefs, attr=None) -> ApiModule: | ||
"""Helper for initpkg. | ||
Python 3.3+ uses finer grained locking for imports, and checks sys.modules before | ||
acquiring the lock to avoid the overhead of the fine-grained locking. This | ||
introduces a race condition when a module is imported by multiple threads | ||
concurrently - some threads will see the initial module and some the replacement | ||
ApiModule. We avoid this by updating the existing module in-place. | ||
""" | ||
if mod is None: | ||
d = {"__file__": None, "__spec__": None} | ||
d.update(attr) | ||
mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d) | ||
sys.modules[pkgname] = mod | ||
return mod | ||
else: | ||
f = getattr(mod, "__file__", None) | ||
if f: | ||
f = _py_abspath(f) | ||
mod.__file__ = f | ||
if hasattr(mod, "__path__"): | ||
mod.__path__ = [_py_abspath(p) for p in mod.__path__] | ||
if "__doc__" in exportdefs and hasattr(mod, "__doc__"): | ||
del mod.__doc__ | ||
for name in dir(mod): | ||
if name not in _PRESERVED_MODULE_ATTRS: | ||
delattr(mod, name) | ||
|
||
# Updating class of existing module as per importlib.util.LazyLoader | ||
mod.__class__ = ApiModule | ||
apimod = cast(ApiModule, mod) | ||
ApiModule.__init__(apimod, pkgname, exportdefs, implprefix=pkgname, attr=attr) | ||
return apimod |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from __future__ import annotations | ||
|
||
import functools | ||
import threading | ||
|
||
|
||
def _synchronized(wrapped_function): | ||
"""Decorator to synchronise __getattr__ calls.""" | ||
|
||
# Lock shared between all instances of ApiModule to avoid possible deadlocks | ||
lock = threading.RLock() | ||
|
||
@functools.wraps(wrapped_function) | ||
def synchronized_wrapper_function(*args, **kwargs): | ||
with lock: | ||
return wrapped_function(*args, **kwargs) | ||
|
||
return synchronized_wrapper_function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
version = "3.0.2" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,29 @@ | ||
from cellpy.libs.apipkg import initpkg | ||
|
||
initpkg( | ||
__name__, | ||
{ | ||
"externals": { | ||
"numpy": "numpy", | ||
"openpyxl": "openpyxl", | ||
"pandas": "pandas", | ||
"pint": "pint", | ||
}, | ||
# "core": { | ||
# "Data": "cellpy.readers.core:Data", | ||
# "BaseDbReader": "cellpy.readers.core:BaseDbReader", | ||
# "FileID": "cellpy.readers.core:FileID", | ||
# "Q": "cellpy.readers.core:Q", | ||
# "convert_from_simple_unit_label_to_string_unit_label": "cellpy.readers.core:convert_from_simple_unit_label_to_string_unit_label", | ||
# "generate_default_factory": "cellpy.readers.core:generate_default_factory", | ||
# "identify_last_data_point": "cellpy.readers.core:identify_last_data_point", | ||
# "instrument_configurations": "cellpy.readers.core:instrument_configurations", | ||
# "interpolate_y_on_x": "cellpy.readers.core:interpolate_y_on_x", | ||
# "pickle_protocol": "cellpy.readers.core:pickle_protocol", | ||
# "xldate_as_datetime": "cellpy.readers.core:xldate_as_datetime", | ||
# }, | ||
# "internals": { | ||
# "OtherPath": "cellpy.internals.core:OtherPath", | ||
# }, | ||
}, | ||
) |
Oops, something went wrong.