| // Lazy object implementation. | |
| #include "Python.h" | |
| #include "pycore_ceval.h" | |
| #include "pycore_dict.h" | |
| #include "pycore_gc.h" | |
| #include "pycore_import.h" | |
| #include "pycore_interpframe.h" | |
| #include "pycore_lazyimportobject.h" | |
| #include "pycore_long.h" | |
| #include "pycore_moduleobject.h" | |
| #include "pycore_pyatomic_ft_wrappers.h" | |
| #include "pycore_pyerrors.h" | |
| #include "pycore_traceback.h" | |
| #include "pycore_tstate.h" | |
| typedef struct { | |
| PyObject_HEAD | |
| PyObject *lz_builtins; // Roots own the mapping; projections retain the root. | |
| // A root stores its absolute name (PyUnicode) in lz_from, and original | |
| // fromlist in lz_attr. | |
| // A projection stores its source placeholder (a PyLazyImportObject) | |
| // in lz_from, and the attribute to import from (PyUnicode) it in lz_attr. | |
| PyObject *lz_from; | |
| PyObject *lz_attr; | |
| // Declaration location. | |
| PyCodeObject *lz_code; | |
| int lz_instr_offset; | |
| // The root was accessed, so its declared path requires import semantics. | |
| int lz_active; | |
| } PyLazyImportObject; | |
| #define PyLazyImportObject_CAST(op) ((PyLazyImportObject *)(op)) | |
| static PyObject *lazy_import_name(PyLazyImportObject *m); | |
| PyObject * | |
| _PyLazyImport_New(_PyInterpreterFrame *frame, PyObject *builtins, | |
| PyObject *name, PyObject *fromlist) | |
| { | |
| if (!name || !(PyUnicode_Check(name) || PyLazyImport_CheckExact(name))) { | |
| PyErr_SetString(PyExc_TypeError, "expected str or lazy_import for name"); | |
| return NULL; | |
| } | |
| if (fromlist == Py_None || fromlist == NULL) { | |
| fromlist = NULL; | |
| } | |
| else if (!PyUnicode_Check(fromlist) && !PyTuple_Check(fromlist)) { | |
| PyErr_SetString(PyExc_TypeError, | |
| "lazy_import: fromlist must be None, a string, or a tuple"); | |
| return NULL; | |
| } | |
| #ifndef NDEBUG | |
| if (PyLazyImport_CheckExact(name)) { | |
| // projection | |
| assert(builtins == NULL); | |
| assert(fromlist != NULL); | |
| assert(PyUnicode_Check(fromlist)); | |
| } | |
| else { | |
| // root | |
| assert(builtins != NULL); | |
| } | |
| #endif | |
| PyLazyImportObject *m = PyObject_GC_New( | |
| PyLazyImportObject, &PyLazyImport_Type); | |
| if (m == NULL) { | |
| return NULL; | |
| } | |
| m->lz_builtins = Py_XNewRef(builtins); | |
| m->lz_from = Py_NewRef(name); | |
| m->lz_attr = Py_XNewRef(fromlist); | |
| m->lz_code = NULL; | |
| m->lz_instr_offset = -1; | |
| m->lz_active = 0; | |
| if (frame != NULL) { | |
| m->lz_code = (PyCodeObject *)Py_NewRef(_PyFrame_GetCode(frame)); | |
| m->lz_instr_offset = _PyInterpreterFrame_LASTI(frame); | |
| } | |
| _PyObject_GC_TRACK(m); | |
| return (PyObject *)m; | |
| } | |
| // Reuse concrete attributes of initialized modules without waiting for imports | |
| // or resolving lazy attributes. Failed cache lookups are retried at resolution. | |
| // May return NULL with or without an exception set. | |
| static PyObject * | |
| lazy_import_get_loaded_attr(PyThreadState *tstate, PyObject *name, | |
| PyObject *attr_name) | |
| { | |
| PyObject *mod = NULL, *spec = NULL, *current = NULL, *attr = NULL; | |
| PyObject *modules = Py_XNewRef(_PyImport_GetModules(tstate->interp)); | |
| if (modules == NULL) { | |
| return NULL; | |
| } | |
| int rc = PyMapping_GetOptionalItem(modules, name, &mod); | |
| if (rc <= 0 || !PyModule_Check(mod)) { | |
| goto done; | |
| } | |
| PyObject *dict = _PyModule_GetDict(mod); | |
| if (PyObject_GetOptionalAttr(mod, &_Py_ID(__spec__), &spec) < 0 || | |
| _PyModuleSpec_IsInitializing(spec) != 0) { | |
| goto done; | |
| } | |
| // An initialization check can replace the module in sys.modules. | |
| if (modules != _PyImport_GetModules(tstate->interp) || | |
| PyMapping_GetOptionalItem(modules, name, ¤t) <= 0 || | |
| current != mod) { | |
| goto done; | |
| } | |
| if (PyDict_GetItemRef(dict, attr_name, &attr) < 0) { | |
| goto done; | |
| } | |
| if (attr != NULL && PyLazyImport_CheckExact(attr)) { | |
| Py_CLEAR(attr); | |
| } | |
| done: | |
| Py_XDECREF(current); | |
| Py_XDECREF(spec); | |
| Py_XDECREF(mod); | |
| Py_DECREF(modules); | |
| if (PyErr_ExceptionMatches(PyExc_Exception)) { | |
| PyErr_Clear(); | |
| } | |
| return attr; | |
| } | |
| PyObject * | |
| _PyEval_LazyImportFrom(PyThreadState *tstate, _PyInterpreterFrame *frame, | |
| PyObject *v, PyObject *name) | |
| { | |
| assert(PyLazyImport_CheckExact(v)); | |
| assert(name); | |
| assert(PyUnicode_Check(name)); | |
| PyLazyImportObject *lz = PyLazyImportObject_CAST(v); | |
| // Only `from a import b` can take b off an already imported a; | |
| // `import a.b as c` has to import a.b first. | |
| if (lz->lz_attr != NULL && PyTuple_Check(lz->lz_attr) && | |
| PyTuple_GET_SIZE(lz->lz_attr) > 0) { | |
| PyObject *attr = lazy_import_get_loaded_attr(tstate, lz->lz_from, name); | |
| if (attr != NULL || PyErr_Occurred()) { | |
| return attr; | |
| } | |
| } | |
| return _PyLazyImport_New(frame, NULL, v, name); | |
| } | |
| static int | |
| lazy_import_traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| PyLazyImportObject *m = PyLazyImportObject_CAST(op); | |
| Py_VISIT(m->lz_builtins); | |
| Py_VISIT(m->lz_from); | |
| Py_VISIT(m->lz_attr); | |
| Py_VISIT(m->lz_code); | |
| return 0; | |
| } | |
| static int | |
| lazy_import_clear(PyObject *op) | |
| { | |
| PyLazyImportObject *m = PyLazyImportObject_CAST(op); | |
| Py_CLEAR(m->lz_builtins); | |
| Py_CLEAR(m->lz_from); | |
| Py_CLEAR(m->lz_attr); | |
| Py_CLEAR(m->lz_code); | |
| return 0; | |
| } | |
| static void | |
| lazy_import_dealloc(PyObject *op) | |
| { | |
| _PyObject_GC_UNTRACK(op); | |
| (void)lazy_import_clear(op); | |
| Py_TYPE(op)->tp_free(op); | |
| } | |
| /* Specialize the error message for failed attribute lookups. */ | |
| static PyObject * | |
| lazy_import_getattro(PyObject *op, PyObject *name) | |
| { | |
| PyObject *value = _PyObject_GenericGetAttrWithDict( | |
| op, name, NULL, /* suppress */ 1); | |
| if (value != NULL || PyErr_Occurred()) { | |
| return value; | |
| } | |
| PyObject *lz_name = lazy_import_name(PyLazyImportObject_CAST(op)); | |
| if (lz_name == NULL) { | |
| return NULL; | |
| } | |
| PyErr_Format(PyExc_AttributeError, | |
| "cannot access attribute %R on unresolved lazy import %R", | |
| name, lz_name); | |
| Py_DECREF(lz_name); | |
| return NULL; | |
| } | |
| // The dotted name of the object that resolving the placeholder returns. | |
| static PyObject * | |
| lazy_import_path(PyLazyImportObject *m) | |
| { | |
| if (PyLazyImport_CheckExact(m->lz_from)) { | |
| if (_Py_EnterRecursiveCall(" while getting the name of a lazy import")) { | |
| return NULL; | |
| } | |
| PyObject *base = lazy_import_path((PyLazyImportObject *)m->lz_from); | |
| _Py_LeaveRecursiveCall(); | |
| if (base == NULL) { | |
| return NULL; | |
| } | |
| PyObject *res = PyUnicode_FromFormat("%U.%U", base, m->lz_attr); | |
| Py_DECREF(base); | |
| return res; | |
| } | |
| if (m->lz_attr != NULL && | |
| (!PyTuple_Check(m->lz_attr) || PyTuple_GET_SIZE(m->lz_attr) > 0)) { | |
| return Py_NewRef(m->lz_from); | |
| } | |
| // __import__("a.b") returns the top-level package `a`. | |
| Py_ssize_t dot = PyUnicode_FindChar( | |
| m->lz_from, '.', 0, PyUnicode_GET_LENGTH(m->lz_from), 1 | |
| ); | |
| if (dot == -2) { | |
| return NULL; | |
| } | |
| if (dot < 0) { | |
| return Py_NewRef(m->lz_from); | |
| } | |
| return PyUnicode_Substring(m->lz_from, 0, dot); | |
| } | |
| static PyObject * | |
| lazy_import_name(PyLazyImportObject *m) | |
| { | |
| if (PyLazyImport_CheckExact(m->lz_from)) { | |
| return lazy_import_path(m); | |
| } | |
| if (m->lz_attr != NULL && | |
| (!PyTuple_Check(m->lz_attr) || PyTuple_GET_SIZE(m->lz_attr) > 0)) { | |
| return PyUnicode_FromFormat("%U...", m->lz_from); | |
| } | |
| return Py_NewRef(m->lz_from); | |
| } | |
| static PyObject * | |
| lazy_import_repr(PyObject *op) | |
| { | |
| PyLazyImportObject *m = PyLazyImportObject_CAST(op); | |
| PyObject *name = lazy_import_name(m); | |
| if (name == NULL) { | |
| return NULL; | |
| } | |
| PyObject *res = PyUnicode_FromFormat("<%T '%U'>", op, name); | |
| Py_DECREF(name); | |
| return res; | |
| } | |
| // Consume a result, resolving a placeholder returned by an import hook or | |
| // an attribute lookup under the same cycle and recursion checks. | |
| static PyObject * | |
| lazy_import_resolve_result(PyThreadState *tstate, PyObject *obj) | |
| { | |
| if (obj == NULL || !PyLazyImport_CheckExact(obj)) { | |
| return obj; | |
| } | |
| PyObject *result = _PyImport_LoadLazyImportTstate(tstate, obj); | |
| Py_DECREF(obj); | |
| return result; | |
| } | |
| // Look up, in order, the attributes recorded from the root placeholder to lz | |
| // on the module the root's import returned. | |
| static PyObject * | |
| lazy_import_replay_from(PyThreadState *tstate, PyObject *mod, | |
| PyLazyImportObject *lz) | |
| { | |
| if (!PyLazyImport_CheckExact(lz->lz_from)) { | |
| return Py_NewRef(mod); | |
| } | |
| if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) { | |
| return NULL; | |
| } | |
| PyObject *from = lazy_import_replay_from( | |
| tstate, mod, (PyLazyImportObject *)lz->lz_from); | |
| _Py_LeaveRecursiveCallTstate(tstate); | |
| if (from == NULL) { | |
| return NULL; | |
| } | |
| PyObject *obj = _PyEval_ImportFrom(tstate, from, lz->lz_attr); | |
| Py_DECREF(from); | |
| return lazy_import_resolve_result(tstate, obj); | |
| } | |
| // Preserve the resolution error and attach the import's declaration location. | |
| static void | |
| lazy_import_add_exception_cause(PyThreadState *tstate, PyLazyImportObject *lz) | |
| { | |
| if (!PyErr_Occurred() || lz->lz_code == NULL || lz->lz_instr_offset < 0) { | |
| return; | |
| } | |
| PyObject *exc = _PyErr_GetRaisedException(tstate); | |
| PyObject *name = lazy_import_name(lz); | |
| if (name == NULL) { | |
| goto done; | |
| } | |
| int lineno = PyCode_Addr2Line(lz->lz_code, lz->lz_instr_offset * 2); | |
| const char *filename = PyUnicode_AsUTF8(lz->lz_code->co_filename); | |
| if (filename == NULL) { | |
| goto done; | |
| } | |
| const char *funcname = PyUnicode_AsUTF8(lz->lz_code->co_name); | |
| if (funcname == NULL) { | |
| goto done; | |
| } | |
| PyBaseExceptionObject *base_exc = (PyBaseExceptionObject *)exc; | |
| if (base_exc->cause != NULL || base_exc->context != NULL || | |
| base_exc->suppress_context) { | |
| // Preserve the original chain, including `raise ... from None`. | |
| PyObject *note = PyUnicode_FromFormat( | |
| "lazy import of '%U' declared in %s at %s:%d", | |
| name, funcname, filename, lineno); | |
| if (note != NULL) { | |
| PyObject *notes; | |
| if (PyObject_GetOptionalAttr(exc, &_Py_ID(__notes__), ¬es) >= 0) { | |
| if (notes == NULL || PySequence_Contains(notes, note) == 0) { | |
| (void)_PyException_AddNote(exc, note); | |
| } | |
| Py_XDECREF(notes); | |
| } | |
| Py_DECREF(note); | |
| } | |
| goto done; | |
| } | |
| PyObject *msg = PyUnicode_FromFormat( | |
| "lazy import of '%U' raised an exception during resolution", name); | |
| if (msg == NULL) { | |
| goto done; | |
| } | |
| PyObject *cause = PyObject_CallOneArg(PyExc_ImportError, msg); | |
| Py_DECREF(msg); | |
| if (cause == NULL) { | |
| goto done; | |
| } | |
| _PyErr_SetRaisedException(tstate, cause); | |
| _PyTraceback_Add(funcname, filename, lineno); | |
| PyException_SetCause(exc, _PyErr_GetRaisedException(tstate)); | |
| done: | |
| Py_XDECREF(name); | |
| _PyErr_SetRaisedException(tstate, exc); | |
| } | |
| int | |
| _PyLazyImport_IsResolving(PyThreadState *tstate, PyObject *op) | |
| { | |
| _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; | |
| assert(PyLazyImport_CheckExact(op)); | |
| int active = ts->lazy_imports == NULL ? 0 : PySet_Contains(ts->lazy_imports, op); | |
| assert(active >= 0); // Exact placeholders use identity hashing and equality. | |
| return active; | |
| } | |
| static PyObject * | |
| lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import, | |
| PyObject **imported_module) | |
| { | |
| PyObject *obj = NULL; | |
| PyObject *fromlist = NULL; | |
| PyObject *import_func = NULL; | |
| PyObject *resolving = NULL; | |
| assert(lazy_import != NULL); | |
| assert(PyLazyImport_CheckExact(lazy_import)); | |
| PyLazyImportObject *lz = (PyLazyImportObject *)lazy_import; | |
| // Walk back to the placeholder IMPORT_NAME left, and the first lookup on it. | |
| PyLazyImportObject *root = lz, *first = NULL; | |
| while (PyLazyImport_CheckExact(root->lz_from)) { | |
| first = root; | |
| root = (PyLazyImportObject *)root->lz_from; | |
| } | |
| if (_PyLazyImport_IsResolving(tstate, lazy_import)) { | |
| PyObject *name = lazy_import_name(lz); | |
| if (name == NULL) { | |
| return NULL; | |
| } | |
| PyObject *errmsg = PyUnicode_FromFormat( | |
| "cannot import name %R (most likely due to a circular import)", | |
| name); | |
| Py_DECREF(name); | |
| if (errmsg != NULL) { | |
| PyErr_SetImportErrorSubclass(PyExc_ImportCycleError, errmsg, | |
| root->lz_from, NULL); | |
| Py_DECREF(errmsg); | |
| } | |
| return NULL; | |
| } | |
| if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) { | |
| return NULL; | |
| } | |
| _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; | |
| if (ts->lazy_imports == NULL) { | |
| ts->lazy_imports = PySet_New(NULL); | |
| if (ts->lazy_imports == NULL) { | |
| goto done; | |
| } | |
| } | |
| resolving = ts->lazy_imports; | |
| if (PySet_Add(resolving, lazy_import) < 0) { | |
| goto done; | |
| } | |
| // `from a import b, c`: import only the name being resolved. | |
| // Keep an empty tuple intact for custom __import__ hooks. | |
| if (first != NULL && root->lz_attr != NULL && | |
| PyTuple_Check(root->lz_attr) && PyTuple_GET_SIZE(root->lz_attr) > 0) { | |
| fromlist = PyTuple_Pack(1, first->lz_attr); | |
| } | |
| else { | |
| fromlist = Py_NewRef(root->lz_attr != NULL ? root->lz_attr : Py_None); | |
| } | |
| if (fromlist == NULL) { | |
| goto done; | |
| } | |
| PyObject *globals = PyEval_GetGlobals(); | |
| if (globals == NULL) { | |
| globals = Py_None; | |
| } | |
| if (PyMapping_GetOptionalItem(root->lz_builtins, &_Py_ID(__import__), | |
| &import_func) < 0) { | |
| goto done; | |
| } | |
| if (import_func == NULL) { | |
| PyErr_SetString(PyExc_ImportError, "__import__ not found"); | |
| goto done; | |
| } | |
| PyObject *name = Py_NewRef(root->lz_from); | |
| if (first == NULL && | |
| (fromlist == Py_None || | |
| (PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist) == 0)) && | |
| _PyImport_IsDefaultImportFunc(tstate->interp, import_func) && | |
| root->lz_builtins == tstate->interp->builtins) { | |
| // A plain import binds the root package. Its pending children are | |
| // imported separately when their attributes are accessed. | |
| Py_ssize_t dot = PyUnicode_FindChar( | |
| name, '.', 0, PyUnicode_GET_LENGTH(name), 1); | |
| if (dot >= 0) { | |
| // Preserve eager traversal through aliased or non-package parents. | |
| int regular = 1; | |
| int complete = 1; | |
| Py_ssize_t end = PyUnicode_GET_LENGTH(name); | |
| for (Py_ssize_t i = dot; i >= 0 && regular; | |
| i = PyUnicode_FindChar(name, '.', i + 1, end, 1)) { | |
| PyObject *prefix = PyUnicode_Substring(name, 0, i); | |
| PyObject *cached = prefix == NULL ? NULL : | |
| lazy_import_get_loaded_attr(tstate, prefix, &_Py_ID(__name__)); | |
| if (cached == NULL) { | |
| complete = 0; | |
| Py_XDECREF(prefix); | |
| break; | |
| } | |
| PyObject *path = lazy_import_get_loaded_attr( | |
| tstate, prefix, &_Py_ID(__path__)); | |
| regular = path != NULL && PyUnicode_Check(cached) && | |
| PyUnicode_Compare(cached, prefix) == 0; | |
| Py_XDECREF(path); | |
| Py_DECREF(cached); | |
| Py_DECREF(prefix); | |
| } | |
| PyObject *loaded = lazy_import_get_loaded_attr( | |
| tstate, name, &_Py_ID(__name__)); | |
| if (regular && (!complete || loaded == NULL) && !PyErr_Occurred()) { | |
| Py_SETREF(name, PyUnicode_Substring(name, 0, dot)); | |
| } | |
| Py_XDECREF(loaded); | |
| if (PyErr_Occurred()) { | |
| Py_CLEAR(name); | |
| } | |
| } | |
| else if (dot == -2) { | |
| Py_CLEAR(name); | |
| } | |
| if (name == NULL) { | |
| goto done; | |
| } | |
| } | |
| obj = _PyEval_ImportNameWithImport( | |
| tstate, import_func, globals, globals, | |
| name, fromlist, _PyLong_GetZero() | |
| ); | |
| if (obj != NULL && name != root->lz_from) { | |
| FT_ATOMIC_STORE_INT_RELAXED(root->lz_active, 1); | |
| FT_ATOMIC_STORE_INT_RELAXED(tstate->interp->imports.has_lazy_submodules, 1); | |
| int package = PyModule_CheckExact(obj) ? | |
| PyDict_Contains(_PyModule_GetDict(obj), &_Py_ID(__path__)) : 0; | |
| if (package < 0) { | |
| Py_CLEAR(obj); | |
| } | |
| else if (!package) { | |
| Py_DECREF(obj); | |
| obj = _PyEval_ImportNameWithImport( | |
| tstate, import_func, globals, globals, | |
| root->lz_from, fromlist, _PyLong_GetZero()); | |
| } | |
| } | |
| if (obj != NULL && name == root->lz_from && | |
| _PyImport_IsDefaultImportFunc(tstate->interp, import_func) && | |
| _PyImport_ClearLazySubmodule(tstate, name, 0) < 0) { | |
| Py_CLEAR(obj); | |
| } | |
| Py_DECREF(name); | |
| obj = lazy_import_resolve_result(tstate, obj); | |
| // The normal importer may publish this module on its parent. Custom | |
| // hooks retain control of their own assignments to the parent. | |
| if (imported_module != NULL && obj != NULL && PyModule_Check(obj) && | |
| _PyImport_IsDefaultImportFunc(tstate->interp, import_func)) { | |
| *imported_module = Py_NewRef(obj); | |
| } | |
| if (obj != NULL && first != NULL) { | |
| // Keep the hook and root result alive until all attribute lookups finish. | |
| PyObject *from = obj; | |
| obj = lazy_import_replay_from(tstate, from, lz); | |
| Py_DECREF(from); | |
| } | |
| done: | |
| if (obj == NULL) { | |
| lazy_import_add_exception_cause(tstate, lz); | |
| } | |
| assert(obj == NULL || !PyLazyImport_CheckExact(obj)); | |
| if (obj != NULL) { | |
| PyObject *name = lazy_import_path(lz); | |
| if (name == NULL || | |
| _PyImport_DiscardLazyModule(tstate->interp, name) < 0) { | |
| Py_CLEAR(obj); | |
| } | |
| Py_XDECREF(name); | |
| } | |
| if (resolving != NULL) { | |
| // A failed set resize can leave the placeholder inserted. Removing by | |
| // identity also permits greenlets to finish in a different order. | |
| if (PySet_Discard(resolving, lazy_import) < 0) { | |
| Py_CLEAR(obj); | |
| } | |
| if (PySet_GET_SIZE(resolving) == 0) { | |
| // Keep the set, but release the capacity used by deep resolutions. | |
| (void)PySet_Clear(resolving); | |
| } | |
| } | |
| Py_XDECREF(fromlist); | |
| Py_XDECREF(import_func); | |
| _Py_LeaveRecursiveCallTstate(tstate); | |
| return obj; | |
| } | |
| PyObject * | |
| _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) | |
| { | |
| return lazy_import_resolve_impl(tstate, lazy_import, NULL); | |
| } | |
| // Resolve a pending child as an aliased import, preserving its declaration. | |
| PyObject * | |
| _PyLazyImport_LoadChild(PyThreadState *tstate, PyObject *declaration, | |
| PyObject *name) | |
| { | |
| assert(PyLazyImport_CheckExact(declaration)); | |
| PyLazyImportObject *source = PyLazyImportObject_CAST(declaration); | |
| PyObject *lz = _PyLazyImport_New(NULL, source->lz_builtins, name, NULL); | |
| Py_ssize_t end = PyUnicode_GET_LENGTH(name); | |
| Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, end, 1); | |
| if (dot == -2) { | |
| Py_CLEAR(lz); | |
| } | |
| while (lz != NULL && dot >= 0) { | |
| Py_ssize_t start = dot + 1; | |
| dot = PyUnicode_FindChar(name, '.', start, end, 1); | |
| if (dot == -2) { | |
| Py_CLEAR(lz); | |
| break; | |
| } | |
| PyObject *attr = PyUnicode_Substring(name, start, dot < 0 ? end : dot); | |
| PyObject *next = attr == NULL ? NULL : | |
| _PyLazyImport_New(NULL, NULL, lz, attr); | |
| Py_XDECREF(attr); | |
| Py_SETREF(lz, next); | |
| } | |
| if (lz == NULL) { | |
| return NULL; | |
| } | |
| PyLazyImportObject *child = PyLazyImportObject_CAST(lz); | |
| child->lz_code = (PyCodeObject *)Py_XNewRef(source->lz_code); | |
| child->lz_instr_offset = source->lz_instr_offset; | |
| PyObject *result = _PyImport_LoadLazyImportTstate(tstate, lz); | |
| Py_DECREF(lz); | |
| if (result != NULL && PyModule_Check(result) && | |
| PyUnicode_Compare(source->lz_from, name) != 0 && | |
| !PyDict_Contains(_PyModule_GetDict(result), &_Py_ID(__path__))) { | |
| // Reaching a non-package must still reject an invalid dotted import. | |
| Py_DECREF(result); | |
| return _PyLazyImport_LoadChild(tstate, declaration, source->lz_from); | |
| } | |
| return result; | |
| } | |
| int | |
| _PyLazyImport_IsActive(PyObject *declaration) | |
| { | |
| assert(PyLazyImport_CheckExact(declaration)); | |
| return FT_ATOMIC_LOAD_INT_RELAXED( | |
| PyLazyImportObject_CAST(declaration)->lz_active); | |
| } | |
| // Loading pkg.child can replace a placeholder in pkg.child with the module | |
| // before a from-import retrieves the value that belongs in that binding. | |
| // This is an optimization that can be safely skipped. | |
| static int | |
| lazy_import_replace_child(PyThreadState *tstate, PyObject *placeholder, | |
| PyObject *name, PyObject *namespace, | |
| PyObject *child, PyObject *value) | |
| { | |
| PyLazyImportObject *root = (PyLazyImportObject *)placeholder; | |
| if (!PyLazyImport_CheckExact(root->lz_from)) { | |
| return 0; | |
| } | |
| while (PyLazyImport_CheckExact(root->lz_from)) { | |
| root = (PyLazyImportObject *)root->lz_from; | |
| } | |
| Py_ssize_t end = PyUnicode_GET_LENGTH(root->lz_from); | |
| Py_ssize_t dot = PyUnicode_FindChar(root->lz_from, '.', 0, end, -1); | |
| if (dot < 0) { | |
| return dot == -1 ? 0 : -1; | |
| } | |
| if (end - dot - 1 != PyUnicode_GET_LENGTH(name)) { | |
| return 0; | |
| } | |
| Py_ssize_t matches = PyUnicode_Tailmatch(root->lz_from, name, dot + 1, end, 1); | |
| if (matches <= 0) { | |
| return matches ? -1 : 0; | |
| } | |
| PyObject *parent_name = PyUnicode_Substring(root->lz_from, 0, dot); | |
| if (parent_name == NULL) { | |
| return -1; | |
| } | |
| PyObject *modules = Py_XNewRef(_PyImport_GetModules(tstate->interp)); | |
| PyObject *parent = NULL; | |
| int rc = 0; | |
| if (modules != NULL) { | |
| rc = PyMapping_GetOptionalItem(modules, parent_name, &parent); | |
| if (rc > 0 && PyModule_Check(parent) && | |
| _PyModule_GetDict(parent) == namespace) { | |
| rc = _PyDict_ReplaceItemIf(namespace, name, child, value); | |
| } | |
| } | |
| Py_XDECREF(parent); | |
| Py_XDECREF(modules); | |
| Py_DECREF(parent_name); | |
| return rc; | |
| } | |
| PyObject * | |
| _PyLazyImport_Reify(PyThreadState *tstate, PyObject *placeholder, | |
| PyObject *name, PyObject *namespace) | |
| { | |
| PyObject *imported_module = NULL; | |
| PyObject *value = lazy_import_resolve_impl( | |
| tstate, placeholder, &imported_module); | |
| if (value == NULL) { | |
| Py_XDECREF(imported_module); | |
| return NULL; | |
| } | |
| int rc; | |
| if (PyDict_CheckExact(namespace)) { | |
| rc = _PyDict_ReplaceItemIf(namespace, name, placeholder, value); | |
| if (rc == 0 && imported_module != NULL) { | |
| rc = lazy_import_replace_child( | |
| tstate, placeholder, name, namespace, imported_module, value); | |
| } | |
| } | |
| else if (Py_TYPE(namespace)->tp_as_mapping == NULL || | |
| Py_TYPE(namespace)->tp_as_mapping->mp_ass_subscript == NULL) { | |
| // Read-only namespaces can resolve a value without caching it. | |
| Py_XDECREF(imported_module); | |
| return value; | |
| } | |
| else { | |
| // Custom namespaces retain their mapping protocol. Atomic replacement | |
| // is only available for exact dictionaries. | |
| PyObject *current; | |
| rc = PyMapping_GetOptionalItem(namespace, name, ¤t); | |
| if (rc > 0) { | |
| if (current == placeholder) { | |
| rc = PyObject_SetItem(namespace, name, value); | |
| } | |
| Py_DECREF(current); | |
| } | |
| } | |
| if (rc < 0) { | |
| Py_CLEAR(value); | |
| } | |
| Py_XDECREF(imported_module); | |
| return value; | |
| } | |
| static PyObject * | |
| lazy_import_resolve(PyObject *self, PyObject *args) | |
| { | |
| return _PyImport_LoadLazyImportTstate(PyThreadState_GET(), self); | |
| } | |
| static PyMethodDef lazy_import_methods[] = { | |
| { | |
| "resolve", lazy_import_resolve, METH_NOARGS, | |
| PyDoc_STR("Resolve the lazy import and return the imported object.") | |
| }, | |
| {NULL, NULL} | |
| }; | |
| PyDoc_STRVAR(lazy_import_doc, | |
| "lazy_import(builtins, name, fromlist=None, /)\n" | |
| "--\n" | |
| "\n" | |
| "Represents a lazy import that will be resolved on first use.\n" | |
| "\n" | |
| "Instances of this object accessed from the global scope will be\n" | |
| "automatically imported based upon their name and then replaced with\n" | |
| "the imported value."); | |
| PyTypeObject PyLazyImport_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| .tp_name = "lazy_import", | |
| .tp_basicsize = sizeof(PyLazyImportObject), | |
| .tp_dealloc = lazy_import_dealloc, | |
| .tp_repr = lazy_import_repr, | |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, | |
| .tp_doc = lazy_import_doc, | |
| .tp_getattro = lazy_import_getattro, | |
| .tp_traverse = lazy_import_traverse, | |
| .tp_clear = lazy_import_clear, | |
| .tp_methods = lazy_import_methods, | |
| .tp_alloc = PyType_GenericAlloc, | |
| .tp_free = PyObject_GC_Del, | |
| }; |