Skip to content

Commit ed7075e

Browse files
authored
Merge pull request #242 from AdvancedPhotonSource/autoImportCode
revisit importers and exporters and add a method for local additions (in ~/.GSASII/imports or ~/.GSASII/exports)
2 parents b4c4db5 + 5561731 commit ed7075e

File tree

4 files changed

+326
-171
lines changed

4 files changed

+326
-171
lines changed

GSASII/GSASIIdataGUI.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ def _Add_ImportMenu_Phase(self,parent):
11041104
submenu = wx.Menu()
11051105
item = parent.AppendSubMenu(submenu,'Phase','Import phase data')
11061106
for reader in self.ImportPhaseReaderlist:
1107-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
1107+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
11081108
self.ImportMenuId[item.GetId()] = reader
11091109
self.Bind(wx.EVT_MENU, self.OnImportPhase, id=item.GetId())
11101110
item = submenu.Append(wx.ID_ANY,'guess format from file','Import phase data, use file to try to determine format')
@@ -1291,7 +1291,7 @@ def _Add_ImportMenu_Image(self,parent):
12911291
submenu = wx.Menu()
12921292
item = parent.AppendSubMenu(submenu, 'Image','Import image file')
12931293
for reader in self.ImportImageReaderlist:
1294-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
1294+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
12951295
self.ImportMenuId[item.GetId()] = reader
12961296
self.Bind(wx.EVT_MENU, self.OnImportImage, id=item.GetId())
12971297
item = submenu.Append(wx.ID_ANY,'guess format from file','Import image data, use file to try to determine format')
@@ -1321,7 +1321,7 @@ def _Add_ImportMenu_Sfact(self,parent):
13211321
submenu = wx.Menu()
13221322
item = parent.AppendSubMenu(submenu,'Structure Factor','Import Structure Factor data')
13231323
for reader in self.ImportSfactReaderlist:
1324-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
1324+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
13251325
self.ImportMenuId[item.GetId()] = reader
13261326
self.Bind(wx.EVT_MENU, self.OnImportSfact, id=item.GetId())
13271327
item = submenu.Append(wx.ID_ANY,'guess format from file','Import Structure Factor, use file to try to determine format')
@@ -1437,7 +1437,7 @@ def OnAutoImport(event):
14371437
submenu = wx.Menu()
14381438
item = parent.AppendSubMenu(submenu,'Powder Data','Import Powder data')
14391439
for reader in self.ImportPowderReaderlist:
1440-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
1440+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
14411441
self.ImportMenuId[item.GetId()] = reader
14421442
self.Bind(wx.EVT_MENU, self.OnImportPowder, id=item.GetId())
14431443
item = submenu.Append(wx.ID_ANY,'guess format from file','Import powder data, use file to try to determine format')
@@ -2371,7 +2371,7 @@ def _Add_ImportMenu_smallangle(self,parent):
23712371
submenu = wx.Menu()
23722372
item = parent.AppendSubMenu(submenu,'Small Angle Data','Import small angle data')
23732373
for reader in self.ImportSmallAngleReaderlist:
2374-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
2374+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
23752375
self.ImportMenuId[item.GetId()] = reader
23762376
self.Bind(wx.EVT_MENU, self.OnImportSmallAngle, id=item.GetId())
23772377
# item = submenu.Append(wx.ID_ANY,
@@ -2464,7 +2464,7 @@ def _Add_ImportMenu_reflectometry(self,parent):
24642464
submenu = wx.Menu()
24652465
item = parent.AppendSubMenu(submenu,'Reflectometry Data','Import reflectometry data')
24662466
for reader in self.ImportReflectometryReaderlist:
2467-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
2467+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
24682468
self.ImportMenuId[item.GetId()] = reader
24692469
self.Bind(wx.EVT_MENU, self.OnImportReflectometry, id=item.GetId())
24702470
# item = submenu.Append(wx.ID_ANY,
@@ -2559,7 +2559,7 @@ def _Add_ImportMenu_PDF(self,parent):
25592559
submenu = wx.Menu()
25602560
item = parent.AppendSubMenu(submenu,'PDF G(R) Data','Import PDF G(R) data')
25612561
for reader in self.ImportPDFReaderlist:
2562-
item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName)
2562+
item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName)
25632563
self.ImportMenuId[item.GetId()] = reader
25642564
self.Bind(wx.EVT_MENU, self.OnImportPDF, id=item.GetId())
25652565
submenu.AppendSeparator()
@@ -7512,6 +7512,14 @@ def _makemenu(): # routine to create menu when first used
75127512
self.DataGeneral = _makemenu
75137513
# end of GSAS-II menu definitions
75147514

7515+
def readFromFile(reader):
7516+
'''Define a caption for a file import menu item'''
7517+
nam = reader.formatName
7518+
if nam.startswith('(user'):
7519+
return nam.replace('(user)','(user) from')+' file'
7520+
else:
7521+
return f'from {reader.formatName} file'
7522+
75157523
#### Notebook Tree Item editor ##############################################
75167524
NBinfo = {}
75177525
def UpdateNotebook(G2frame,data):

GSASII/GSASIIfiles.py

Lines changed: 115 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -579,173 +579,155 @@ def WriteInstprm(fp, InstPrm, InstPrm1, Sample={}, bank=None):
579579
#write output string
580580
fp.write(resString)
581581

582-
# version of LoadImportRoutines from before switch to "main"
583-
# def _old_LoadImportRoutines(prefix, errprefix=None, traceback=False):
584-
# '''Routine to locate GSASII importers matching a prefix string.
585-
#
586-
# Warns if more than one file with the same name is in the path
587-
# or if a file is found that is not in the main directory tree.
588-
# '''
589-
# if errprefix is None:
590-
# errprefix = prefix
591-
#
592-
# readerlist = []
593-
# import_files = {}
594-
# if '.' not in sys.path: sys.path.append('.')
595-
# for path in sys.path:
596-
# for filename in glob.iglob(os.path.join(path, 'G2'+prefix+'*.py')):
597-
# pkg = os.path.splitext(os.path.split(filename)[1])[0]
598-
# if pkg in import_files:
599-
# G2Print('Warning: importer {} overrides {}'.format(import_files[pkg],os.path.abspath(filename)))
600-
# elif not filename.startswith(GSASIIpath.path2GSAS2):
601-
# G2Print('Note: found importer in non-standard location:'+
602-
# f'\n\t{os.path.abspath(filename)}')
603-
# import_files[pkg] = filename
604-
# else:
605-
# import_files[pkg] = filename
606-
607-
# for pkg in sorted(import_files.keys()):
608-
# try:
609-
# exec('import '+pkg)
610-
# #print(eval(pkg+'.__file__'))
611-
# for name, value in inspect.getmembers(eval(pkg)):
612-
# if name.startswith('_'):
613-
# continue
614-
# if inspect.isclass(value):
615-
# for method in 'Reader', 'ExtensionValidator', 'ContentsValidator':
616-
# if not hasattr(value, method):
617-
# break
618-
# if not callable(getattr(value, method)):
619-
# break
620-
# else:
621-
# reader = value()
622-
# if reader.UseReader:
623-
# readerlist.append(reader)
624-
# except AttributeError:
625-
# G2Print ('Import_' + errprefix + ': Attribute Error ' + import_files[pkg])
626-
# if traceback:
627-
# traceback.print_exc(file=sys.stdout)
628-
# except Exception as exc:
629-
# G2Print ('\nImport_' + errprefix + ': Error importing file ' + import_files[pkg])
630-
# G2Print (u'Error message: {}\n'.format(exc))
631-
# if traceback:
632-
# traceback.print_exc(file=sys.stdout)
633-
# return readerlist
634-
635582
def LoadImportRoutines(prefix, errprefix=None, traceback=False):
583+
'''Loads import routines from the GSASII.imports area and
584+
from a user-supplied area, ~/.GSASII/imports.
585+
586+
:param str prefix: string used in the file name to differentiate the
587+
importer type (e.g. `G2img_xyz.py` is an importer for images. Will
588+
be one of 'img' (images), 'pdf' (pair distribution function),
589+
'phase' (cell/coordinates), 'pwd' (powder diffraction), 'rfd'
590+
(reflectivity), 'sad' (small-angle scattering) or 'sfact'
591+
(single crystal).
592+
:param errprefix: no longer used
593+
:param traceback: no longer used
594+
:returns: a list of reader routines (callable objects)
595+
'''
636596
from . import imports
637597
readerlist = []
638-
# how to import directly from a file for extra search magic if we need
639-
# https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path
640-
# TODO handle non-bundled readers
598+
# configure readers that have already been imported
641599
for mod_name in (_ for _ in dir(imports) if _.startswith(f'G2{prefix}')):
642600
mod = getattr(imports, mod_name)
643601
for member_name in dir(mod):
644602
if member_name.startswith('_'):
645603
continue
646604
member = getattr(mod, member_name)
647-
if all(
648-
hasattr(member, meth)
649-
for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator')
650-
):
605+
if not callable(member): continue
606+
for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator','UseReader'):
607+
if not hasattr(member, meth): break
608+
else:
651609
reader = member()
652610
if reader.UseReader:
653611
readerlist.append(reader)
612+
# Now look for modules in the "user-defined" area (~/.GSASII/imports)
613+
fnam = os.path.expanduser(os.path.normpath(f'~/.GSASII/imports/G2{prefix}*.py'))
614+
import importlib.util
615+
for f in sorted(glob.glob(fnam)):
616+
nam = os.path.splitext(os.path.split(f)[1])[0]
617+
try:
618+
modspec = importlib.util.spec_from_file_location(nam, f)
619+
module = importlib.util.module_from_spec(modspec)
620+
# will process "from .." and "from ." as if found in
621+
# GSASII/imports directory
622+
module.__package__ = "GSASII.imports"
623+
modspec.loader.exec_module(module)
624+
# not sure if I want to do this or not
625+
sys.modules[f"GSASII.userimports.{nam}"] = module
626+
for member_name in dir(module):
627+
if member_name.startswith('_'):
628+
continue
629+
member = getattr(module, member_name)
630+
if not callable(member): continue
631+
for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator','UseReader'):
632+
if not hasattr(member, meth): break
633+
else:
634+
reader = member()
635+
reader.formatName = '(user) ' + reader.formatName
636+
reader.longFormatName = '(user supplied) ' + reader.longFormatName
637+
if reader.UseReader:
638+
readerlist.append(reader)
639+
except Exception as msg:
640+
G2Print(f'\nImporter init: error with importer file {f!r}')
641+
G2Print ('Error message: {}\n'.format(msg))
654642
return readerlist
655643

656-
# version of LoadExportRoutines from before switch to "main"
657-
# def _LoadExportRoutines(parent, usetraceback=False):
658-
# '''Routine to locate GSASII exporters. Warns if more than one file
659-
# with the same name is in the path or if a file is found that is not
660-
# in the main directory tree.
661-
# '''
662-
# exporterlist = []
663-
# export_files = {}
664-
# if '.' not in sys.path: sys.path.append('.')
665-
# for path in sys.path:
666-
# for filename in glob.iglob(os.path.join(path,"G2export*.py")):
667-
# pkg = os.path.splitext(os.path.split(filename)[1])[0]
668-
# if pkg in export_files:
669-
# G2Print('Warning: exporter {} overrides {}'.format(export_files[pkg],os.path.abspath(filename)))
670-
# elif not filename.startswith(GSASIIpath.path2GSAS2):
671-
# G2Print('Note, found non-standard exporter: {}'.format(os.path.abspath(filename)))
672-
# export_files[pkg] = filename
673-
# else:
674-
# export_files[pkg] = filename
675-
# # go through the routines and import them, saving objects that
676-
# # have export routines (method Exporter)
677-
# for pkg in sorted(export_files.keys()):
678-
# try:
679-
# exec('import '+pkg)
680-
# for clss in inspect.getmembers(eval(pkg)): # find classes defined in package
681-
# if clss[0].startswith('_'): continue
682-
# if not inspect.isclass(clss[1]): continue
683-
# # check if we have the required methods
684-
# if not hasattr(clss[1],'Exporter'): continue
685-
# if not callable(getattr(clss[1],'Exporter')): continue
686-
# if parent is None:
687-
# if not hasattr(clss[1],'Writer'): continue
688-
# else:
689-
# if not hasattr(clss[1],'loadParmDict'): continue
690-
# if not callable(getattr(clss[1],'loadParmDict')): continue
691-
# try:
692-
# exporter = clss[1](parent) # create an export instance
693-
# except AttributeError:
694-
# pass
695-
# except Exception as exc:
696-
# G2Print ('\nExport init: Error substantiating class ' + clss[0])
697-
# G2Print (u'Error message: {}\n'.format(exc))
698-
# if usetraceback:
699-
# import traceback
700-
# traceback.print_exc(file=sys.stdout)
701-
# continue
702-
# exporterlist.append(exporter)
703-
# except AttributeError:
704-
# G2Print ('Export Attribute Error ' + export_files[pkg])
705-
# if usetraceback:
706-
# import traceback
707-
# traceback.print_exc(file=sys.stdout)
708-
# except Exception as exc:
709-
# G2Print ('\nExport init: Error importing file ' + export_files[pkg])
710-
# G2Print (u'Error message: {}\n'.format(exc))
711-
# if usetraceback:
712-
# import traceback
713-
# traceback.print_exc(file=sys.stdout)
714-
# return exporterlist
715-
716644
def LoadExportRoutines(parent, usetraceback=False):
645+
'''Loads all export routines from the GSASII.exports area and
646+
from a user-supplied area, ~/.GSASII/exports.
647+
648+
:param parent: a reference to the main GSAS-II window when called
649+
from the GUI or None when called from GSASIIscriptable
650+
:param usetraceback: if True (used for debug only) a traceback
651+
is shown when accessing the exporter object fails
652+
:returns: a list of exporter routines (callable objects)
653+
'''
717654
from . import exports
718655
exporterlist = []
719-
# how to import directly from a file for extra search magic if we need
720-
# https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path
721-
# TODO handle non-bundled readers
656+
657+
# configure exporters that have already been imported
722658
for mod_name in (_ for _ in dir(exports) if _.startswith('G2export')):
723659
mod = getattr(exports, mod_name)
724660
for member_name in dir(mod):
725661
if member_name.startswith('_'):
726662
continue
727663
member = getattr(mod, member_name)
728-
if not hasattr(member, 'Exporter'):
664+
if not callable(member): continue
665+
if not hasattr(member, 'Exporter'): # exporters must have an exporter
729666
continue
730-
if parent is None:
731-
if not hasattr(member, 'Writer'):
667+
try:
668+
if parent is None: # scripting, must also have Writer
669+
if not hasattr(member, 'Writer'):
670+
continue
671+
exporter = member(None)
672+
else: # from GUI, must also have loadParmDict
673+
if not hasattr(member, 'loadParmDict'):
674+
continue
675+
exporter = member(parent) # pass the main window to the class
676+
exporterlist.append(exporter)
677+
except Exception as exc:
678+
G2Print (f'\nExport init: Error substantiating class {member_name}')
679+
G2Print ('Error message: {}\n'.format(exc))
680+
if usetraceback:
681+
import traceback
682+
traceback.print_exc(file=sys.stdout)
683+
# Now look for modules in the "user-defined" area (~/.GSASII/exports)
684+
fnam = os.path.expanduser(os.path.normpath('~/.GSASII/exports/G2export*.py'))
685+
import importlib.util
686+
for f in sorted(glob.glob(fnam)):
687+
nam = os.path.splitext(os.path.split(f)[1])[0]
688+
try:
689+
modspec = importlib.util.spec_from_file_location(nam, f)
690+
module = importlib.util.module_from_spec(modspec)
691+
# will process "from .." and "from ." as if found in
692+
# GSASIIexports directory
693+
module.__package__ = "GSASII.exports"
694+
modspec.loader.exec_module(module)
695+
# not sure if I want to do this or not
696+
sys.modules[f"GSASII.userexports.{nam}"] = module
697+
except Exception as exc:
698+
G2Print (f'\nExport init: Error with exporter file {f!r}')
699+
G2Print ('Error message: {}\n'.format(exc))
700+
if usetraceback:
701+
import traceback
702+
traceback.print_exc(file=sys.stdout)
703+
continue
704+
for member_name in dir(module):
705+
try:
706+
if member_name.startswith('_'):
732707
continue
733-
else:
734-
if not hasattr(member, 'loadParmDict'):
708+
member = getattr(module, member_name)
709+
if not hasattr(member, 'Exporter'): # exporters must have an exporter
735710
continue
736-
try:
737-
exporter = member(parent)
711+
if parent is None: # scripting, must also have Writer
712+
if not hasattr(member, 'Writer'):
713+
continue
714+
exporter = member(None)
715+
else: # from GUI, must also have loadParmDict
716+
if not hasattr(member, 'loadParmDict'):
717+
continue
718+
exporter = member(parent) # pass the main window to the class
719+
exporter.formatName = '(user) ' + exporter.formatName
720+
exporter.longFormatName = '(user supplied) ' + exporter.longFormatName
721+
738722
exporterlist.append(exporter)
739723
except Exception as exc:
740-
G2Print ('\nExport init: Error substantiating class ' + member_name)
724+
G2Print (f'\nExport init: Error with substantiating class {member_name}')
741725
G2Print ('Error message: {}\n'.format(exc))
742726
if usetraceback:
743727
import traceback
744728
traceback.print_exc(file=sys.stdout)
745-
continue
746729
return exporterlist
747730

748-
749731
def readColMetadata(imagefile):
750732
'''Reads image metadata from a column-oriented metadata table
751733
(1-ID style .par file). Called by :func:`GetColumnMetadata`

0 commit comments

Comments
 (0)