Aluode/PerceptionLabPortable
0
1from __future__ import annotations2 3import contextlib4import dis5import marshal6import sys7from types import CodeType8from typing import Any, Literal, TypeVar9 10from packaging.version import Version11 12from . import _imp13from ._imp import PY_COMPILED, PY_FROZEN, PY_SOURCE, find_module14 15_T = TypeVar("_T")16 17__all__ = ['Require', 'find_module']18 19 20class Require:21 """A prerequisite to building or installing a distribution"""22 23 def __init__(24 self,25 name,26 requested_version,27 module,28 homepage: str = '',29 attribute=None,30 format=None,31 ) -> None:32 if format is None and requested_version is not None:33 format = Version34 35 if format is not None:36 requested_version = format(requested_version)37 if attribute is None:38 attribute = '__version__'39 40 self.__dict__.update(locals())41 del self.self42 43 def full_name(self):44 """Return full package/distribution name, w/version"""45 if self.requested_version is not None:46 return f'{self.name}-{self.requested_version}'47 return self.name48 49 def version_ok(self, version):50 """Is 'version' sufficiently up-to-date?"""51 return (52 self.attribute is None53 or self.format is None54 or str(version) != "unknown"55 and self.format(version) >= self.requested_version56 )57 58 def get_version(59 self, paths=None, default: _T | Literal["unknown"] = "unknown"60 ) -> _T | Literal["unknown"] | None | Any:61 """Get version number of installed module, 'None', or 'default'62 63 Search 'paths' for module. If not found, return 'None'. If found,64 return the extracted version attribute, or 'default' if no version65 attribute was specified, or the value cannot be determined without66 importing the module. The version is formatted according to the67 requirement's version format (if any), unless it is 'None' or the68 supplied 'default'.69 """70 71 if self.attribute is None:72 try:73 f, _p, _i = find_module(self.module, paths)74 except ImportError:75 return None76 if f:77 f.close()78 return default79 80 v = get_module_constant(self.module, self.attribute, default, paths)81 82 if v is not None and v is not default and self.format is not None:83 return self.format(v)84 85 return v86 87 def is_present(self, paths=None):88 """Return true if dependency is present on 'paths'"""89 return self.get_version(paths) is not None90 91 def is_current(self, paths=None):92 """Return true if dependency is present and up-to-date on 'paths'"""93 version = self.get_version(paths)94 if version is None:95 return False96 return self.version_ok(str(version))97 98 99def maybe_close(f):100 @contextlib.contextmanager101 def empty():102 yield103 return104 105 if not f:106 return empty()107 108 return contextlib.closing(f)109 110 111# Some objects are not available on some platforms.112# XXX it'd be better to test assertions about bytecode instead.113if not sys.platform.startswith('java') and sys.platform != 'cli':114 115 def get_module_constant(116 module, symbol, default: _T | int = -1, paths=None117 ) -> _T | int | None | Any:118 """Find 'module' by searching 'paths', and extract 'symbol'119 120 Return 'None' if 'module' does not exist on 'paths', or it does not define121 'symbol'. If the module defines 'symbol' as a constant, return the122 constant. Otherwise, return 'default'."""123 124 try:125 f, path, (_suffix, _mode, kind) = info = find_module(module, paths)126 except ImportError:127 # Module doesn't exist128 return None129 130 with maybe_close(f):131 if kind == PY_COMPILED:132 f.read(8) # skip magic & date133 code = marshal.load(f)134 elif kind == PY_FROZEN:135 code = _imp.get_frozen_object(module, paths)136 elif kind == PY_SOURCE:137 code = compile(f.read(), path, 'exec')138 else:139 # Not something we can parse; we'll have to import it. :(140 imported = _imp.get_module(module, paths, info)141 return getattr(imported, symbol, None)142 143 return extract_constant(code, symbol, default)144 145 def extract_constant(146 code: CodeType, symbol: str, default: _T | int = -1147 ) -> _T | int | None | Any:148 """Extract the constant value of 'symbol' from 'code'149 150 If the name 'symbol' is bound to a constant value by the Python code151 object 'code', return that value. If 'symbol' is bound to an expression,152 return 'default'. Otherwise, return 'None'.153 154 Return value is based on the first assignment to 'symbol'. 'symbol' must155 be a global, or at least a non-"fast" local in the code block. That is,156 only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'157 must be present in 'code.co_names'.158 """159 if symbol not in code.co_names:160 # name's not there, can't possibly be an assignment161 return None162 163 name_idx = list(code.co_names).index(symbol)164 165 STORE_NAME = dis.opmap['STORE_NAME']166 STORE_GLOBAL = dis.opmap['STORE_GLOBAL']167 LOAD_CONST = dis.opmap['LOAD_CONST']168 169 const = default170 171 for byte_code in dis.Bytecode(code):172 op = byte_code.opcode173 arg = byte_code.arg174 175 if op == LOAD_CONST:176 assert arg is not None177 const = code.co_consts[arg]178 elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL):179 return const180 else:181 const = default182 183 return None184 185 __all__ += ['get_module_constant', 'extract_constant']186 