4848#include " PythonQtImportFileInterface.h"
4949#include " PythonQt.h"
5050#include " PythonQtConversion.h"
51+ #include < QDir>
5152#include < QFile>
5253#include < QFileInfo>
5354
@@ -100,6 +101,13 @@ PythonQtImport::ModuleInfo PythonQtImport::getModuleInfo(PythonQtImporter* self,
100101 QString test;
101102 for (zso = mlab_searchorder; *zso->suffix ; zso++) {
102103 test = path + zso->suffix ;
104+ #ifdef PY3K
105+ if (!PythonQt::importInterface ()->exists (test) && (zso->type & IS_BYTECODE)) {
106+ // try __pycache__/*.pyc file
107+ static QString cacheTag (PyImport_GetMagicTag ());
108+ test = *self->_path + " /__pycache__/" + subname + " ." + cacheTag + zso->suffix ;
109+ }
110+ #endif
103111 if (PythonQt::importInterface ()->exists (test)) {
104112 info.fullPath = test;
105113 info.moduleName = subname;
@@ -227,7 +235,8 @@ PythonQtImporter_load_module(PyObject *obj, PyObject *args)
227235
228236 if (info.type == PythonQtImport::MI_PACKAGE || info.type == PythonQtImport::MI_MODULE) {
229237 QString fullPath;
230- code = PythonQtImport::getModuleCode (self, fullname, fullPath);
238+ QString fullCachePath;
239+ code = PythonQtImport::getModuleCode (self, fullname, fullPath, fullCachePath);
231240 if (code == NULL ) {
232241 return NULL ;
233242 }
@@ -274,9 +283,26 @@ PythonQtImporter_load_module(PyObject *obj, PyObject *args)
274283 Py_DECREF (mod);
275284 return NULL ;
276285 }
286+
287+ // set __package__ only for Python 3, because in Python 2 it causes the exception "__package__ set to non-string"
288+ #ifdef PY3K
289+ // The package attribute is needed to resolve the package name if it is referenced as '.'. For example,
290+ // when importing the encodings package, there is an import statement 'from . import aliases'. This import
291+ // would fail when reloading the encodings package with importlib.
292+ err = PyDict_SetItemString (dict, " __package__" , PyUnicode_FromString (fullname));
293+ if (err != 0 ) {
294+ Py_DECREF (code);
295+ Py_DECREF (mod);
296+ return NULL ;
297+ }
298+ #endif
277299 }
278300
301+ #ifdef PY3K
302+ mod = PyImport_ExecCodeModuleWithPathnames (fullname, code, fullPath.toUtf8 ().data (), fullCachePath.isEmpty () ? NULL : fullCachePath.toUtf8 ().data ());
303+ #else
279304 mod = PyImport_ExecCodeModuleEx (fullname, code, fullPath.toLatin1 ().data ());
305+ #endif
280306
281307 if (PythonQt::importInterface ()) {
282308 PythonQt::importInterface ()->importedModule (fullname);
@@ -351,7 +377,8 @@ PythonQtImporter_get_code(PyObject *obj, PyObject *args)
351377 return NULL ;
352378
353379 QString notused;
354- return PythonQtImport::getModuleCode (self, fullname, notused);
380+ QString notused2;
381+ return PythonQtImport::getModuleCode (self, fullname, notused, notused2);
355382}
356383
357384PyObject *
@@ -517,13 +544,20 @@ open_exclusive(const QString& filename)
517544}
518545
519546
520- void PythonQtImport::writeCompiledModule (PyCodeObject *co, const QString& filename, long mtime)
547+ void PythonQtImport::writeCompiledModule (PyCodeObject *co, const QString& filename, long mtime, long sourceSize )
521548{
522549 FILE *fp;
523550 // we do not want to write Qt resources to disk, do we?
524551 if (filename.startsWith (" :" )) {
525552 return ;
526553 }
554+ #ifdef PY3K
555+ // create __pycache__ directory, if it does not exist
556+ QDir dir = QFileInfo (filename).absoluteDir ();
557+ if (!dir.exists ()) {
558+ dir.mkpath (" ." );
559+ }
560+ #endif
527561 fp = open_exclusive (filename);
528562 if (fp == NULL ) {
529563 if (Py_VerboseFlag)
@@ -542,6 +576,9 @@ void PythonQtImport::writeCompiledModule(PyCodeObject *co, const QString& filena
542576#else
543577 PyMarshal_WriteLongToFile (0L , fp, Py_MARSHAL_VERSION);
544578#endif
579+ #ifdef PY3K
580+ PyMarshal_WriteLongToFile (sourceSize, fp, Py_MARSHAL_VERSION);
581+ #endif
545582#if PY_VERSION_HEX < 0x02040000
546583 PyMarshal_WriteObjectToFile ((PyObject *)co, fp);
547584#else
@@ -611,7 +648,15 @@ PythonQtImport::unmarshalCode(const QString& path, const QByteArray& data, time_
611648 }
612649 }
613650
651+ #ifdef PY3K
652+ // Python 3 also stores the size of the *.py file, but we ignore it for now
653+ int sourceSize = getLong ((unsigned char *)buf + 8 );
654+ Q_UNUSED (sourceSize);
655+ // read the module
656+ code = PyMarshal_ReadObjectFromString (buf + 12 , size - 12 );
657+ #else
614658 code = PyMarshal_ReadObjectFromString (buf + 8 , size - 8 );
659+ #endif
615660 if (code == NULL )
616661 return NULL ;
617662 if (!PyCode_Check (code)) {
@@ -640,6 +685,38 @@ PythonQtImport::compileSource(const QString& path, const QByteArray& data)
640685 return code;
641686}
642687
688+ QString PythonQtImport::getCacheFilename (const QString& sourceFilename, bool isOptimizedFilename)
689+ {
690+ #ifdef PY3K
691+ QFileInfo fi (sourceFilename);
692+ static QString cacheTag (PyImport_GetMagicTag ());
693+ QString pycFilename = fi.absolutePath () + " /__pycache__/" + fi.baseName () + " ." + cacheTag + " .py" ;
694+ #else
695+ QString pycFilename = sourceFilename;
696+ #endif
697+ return pycFilename + (isOptimizedFilename ? " o" : " c" );
698+ }
699+
700+ QString PythonQtImport::getSourceFilename (const QString& cacheFile)
701+ {
702+ #ifdef PY3K
703+ static QString cacheTagPart = " ." + QString (PyImport_GetMagicTag ());
704+ QFileInfo fi (cacheFile);
705+ // get the parent directory of the __pycache__ directory
706+ QDir dir = fi.absoluteDir ();
707+ dir.cdUp ();
708+ QString baseName = fi.completeBaseName ();
709+ baseName.truncate (baseName.length ()-cacheTagPart.length ());
710+ QString pyFilename = dir.absolutePath () + " /" + baseName + " .py" ;
711+ #else
712+ QString pyFilename;
713+ if (cacheFile.length () > 0 ) {
714+ pyFilename = cacheFile;
715+ pyFilename.truncate (pyFilename.length ()-1 );
716+ }
717+ #endif
718+ return pyFilename;
719+ }
643720
644721/* Return the code object for the module named by 'fullname' from the
645722 Zip archive as a new reference. */
@@ -665,7 +742,7 @@ PythonQtImport::getCodeFromData(const QString& path, int isbytecode,int /*ispack
665742 }
666743
667744 if (isbytecode) {
668- // mlabDebugConst("MLABPython", "reading bytecode " << path);
745+ // mlabDebugConst("MLABPython", "reading bytecode " << path);
669746 code = unmarshalCode (path, qdata, mtime);
670747 }
671748 else {
@@ -675,7 +752,8 @@ PythonQtImport::getCodeFromData(const QString& path, int isbytecode,int /*ispack
675752 // save a pyc file if possible
676753 QDateTime time;
677754 time = PythonQt::importInterface ()->lastModifiedDate (path);
678- writeCompiledModule ((PyCodeObject*)code, path+" c" , time.toTime_t ());
755+ QString cacheFilename = getCacheFilename (path, /* isOptimizedFilename=*/ false );
756+ writeCompiledModule ((PyCodeObject*)code, cacheFilename, time.toTime_t (), /* sourceSize=*/ qdata.length ());
679757 }
680758 }
681759 return code;
@@ -685,9 +763,7 @@ time_t
685763PythonQtImport::getMTimeOfSource (const QString& path)
686764{
687765 time_t mtime = 0 ;
688- QString path2 = path;
689- path2.truncate (path.length ()-1 );
690-
766+ QString path2 = getSourceFilename (path);
691767 if (PythonQt::importInterface ()->exists (path2)) {
692768 QDateTime t = PythonQt::importInterface ()->lastModifiedDate (path2);
693769 if (t.isValid ()) {
@@ -701,7 +777,7 @@ PythonQtImport::getMTimeOfSource(const QString& path)
701777/* Get the code object associated with the module specified by
702778 'fullname'. */
703779PyObject *
704- PythonQtImport::getModuleCode (PythonQtImporter *self, const char * fullname, QString& modpath)
780+ PythonQtImport::getModuleCode (PythonQtImporter *self, const char * fullname, QString& modpath, QString& cachemodpath )
705781{
706782 QString subname;
707783 struct st_mlab_searchorder *zso;
@@ -710,13 +786,23 @@ PythonQtImport::getModuleCode(PythonQtImporter *self, const char* fullname, QStr
710786 QString path = *self->_path + " /" + subname;
711787
712788 QString test;
713- for (zso = mlab_searchorder; *zso->suffix ; zso++) {
789+ for (zso = mlab_searchorder; *zso->suffix ;zso++) {
714790 PyObject *code = NULL ;
715791 test = path + zso->suffix ;
716792
717793 if (Py_VerboseFlag > 1 )
718794 PySys_WriteStderr (" # trying %s\n " ,
719795 test.toLatin1 ().constData ());
796+ #ifdef PY3K
797+ if (!PythonQt::importInterface ()->exists (test) && zso->type & IS_BYTECODE) {
798+ // try __pycache__/*.pyc file
799+ static QString cacheTag (PyImport_GetMagicTag ());
800+ test = *self->_path + " /__pycache__/" + subname + " ." + cacheTag + zso->suffix ;
801+ if (Py_VerboseFlag > 1 )
802+ PySys_WriteStderr (" # trying %s\n " ,
803+ test.toLatin1 ().constData ());
804+ }
805+ #endif
720806 if (PythonQt::importInterface ()->exists (test)) {
721807 time_t mtime = 0 ;
722808 int ispackage = zso->type & IS_PACKAGE;
@@ -737,6 +823,12 @@ PythonQtImport::getModuleCode(PythonQtImporter *self, const char* fullname, QStr
737823 }
738824 if (code != NULL ) {
739825 modpath = test;
826+ #ifdef PY3K
827+ if (isbytecode) {
828+ cachemodpath = modpath;
829+ modpath = getSourceFilename (modpath);
830+ }
831+ #endif
740832 }
741833 return code;
742834 }
@@ -762,7 +854,17 @@ PyObject* PythonQtImport::getCodeFromPyc(const QString& file)
762854{
763855 PyObject* code;
764856 const static QString pycStr (" pyc" );
765- QString pyc = replaceExtension (file, pycStr);
857+ QString pyc;
858+ #ifdef PY3K
859+ // if the cache file in __pycache__ does not exist, look for cache file next to
860+ // source file
861+ pyc = getCacheFilename (file, /* isOptimizedFilename=*/ false );
862+ if (!PythonQt::importInterface ()->exists (pyc)) {
863+ pyc = replaceExtension (file, pycStr);
864+ }
865+ #else
866+ pyc = replaceExtension (file, pycStr);
867+ #endif
766868 if (PythonQt::importInterface ()->exists (pyc)) {
767869 time_t mtime = 0 ;
768870 // if ignoreUpdatedPythonSourceFiles() returns true, then mtime stays 0
@@ -857,17 +959,18 @@ void PythonQtImport::init()
857959 // set our importer into the path_hooks to handle all path on sys.path
858960 PyObject* classobj = PyDict_GetItemString (PyModule_GetDict (mod), " PythonQtImporter" );
859961 PyObject* path_hooks = PySys_GetObject (const_cast <char *>(" path_hooks" ));
860- PyList_Append (path_hooks, classobj);
861-
862- #ifndef WIN32
863- // reload the encodings module, because it might fail to custom import requirements (e.g. encryption).
864- PyObject* modules = PyImport_GetModuleDict ();
865- PyObject* encodingsModule = PyDict_GetItemString (modules, " encodings" );
866- if (encodingsModule != NULL ) {
867- PyImport_ReloadModule (encodingsModule);
868- } else {
869- // import it now, so that the search function is registered (a previous import from the codecs module may have failed and it does not retry to import it)
870- PyImport_ImportModule (" encodings" );
871- }
872- #endif
962+ // insert our importer before all other loaders
963+ PyList_Insert (path_hooks, 0 , classobj);
964+
965+ #ifndef WIN32
966+ // reload the encodings module, because it might fail to custom import requirements (e.g. encryption).
967+ PyObject* modules = PyImport_GetModuleDict ();
968+ PyObject* encodingsModule = PyDict_GetItemString (modules, " encodings" );
969+ if (encodingsModule != NULL ) {
970+ PyImport_ReloadModule (encodingsModule);
971+ } else {
972+ // import it now, so that the search function is registered (a previous import from the codecs module may have failed and it does not retry to import it)
973+ PyImport_ImportModule (" encodings" );
974+ }
975+ #endif
873976}
0 commit comments