Ironed out kinks in plugin/driver loading and configuring.
[hooke.git] / hooke / driver / __init__.py
1 """The driver module provides :class:`Driver`\s for identifying and
2 reading data files.
3
4 This allows Hooke to be data-file agnostic.  Drivers for various
5 commercial force spectroscopy microscopes are provided, and it's easy
6 to write your own to handle your lab's specific format.
7 """
8
9 from ..config import Setting
10 from ..util.graph import Node, Graph
11
12
13 DRIVER_MODULES = [
14 #    ('csvdriver', True),
15 #    ('hdf5', True),
16 #    ('hemingclamp', True),
17 #    ('jpk', True),
18 #    ('mcs', True),
19 #    ('mfp1dexport', True),
20 #    ('mfp3d', True),
21 #    ('picoforce', True),
22 #    ('picoforcealt', True),
23     ('tutorial', True),
24 ]
25 """List of driver modules and whether they should be included by
26 default.  TODO: autodiscovery
27 """
28
29 class NotRecognized (ValueError):
30     def __init__(self, path):
31         msg = 'Not a recognizable curve format: %s' % self.path
32         ValueError.__init__(self, msg)
33         self.path = path
34
35 class Driver(object):
36     """Base class for file format drivers.
37     
38     :attr:`name` identifies your driver, and should match the module
39     name.
40     """
41     def __init__(self, name):
42         self.name = name
43         self.setting_section = '%s driver' % self.name
44
45     def dependencies(self):
46         """Return a list of :class:`Driver`\s we require."""
47         return []
48
49     def default_settings(self):
50         """Return a list of :class:`hooke.config.Setting`\s for any
51         configurable driver settings.
52
53         The suggested section setting is::
54
55             Setting(section=self.setting_section, help=self.__doc__)
56         """
57         return []
58
59     def is_me(self, path):
60         """Read the file and return True if the filetype can be
61         managed by the driver.  Otherwise return False.
62         """
63         return False
64
65     def read(self, path):
66         """Read data from `path` and return a
67         (:class:`hooke.curve.Data`, `info`) tuple.
68
69         The `info` :class:`dict` must contain values for the keys:
70         'filetype' and 'experiment'.  See :class:`hooke.curve.Curve`
71         for details.
72         """
73         raise NotImplementedError
74
75 # Construct driver dependency graph and load default drivers.
76
77 DRIVERS = {}
78 """(name, instance) :class:`dict` of all possible :class:`Driver`\s.
79 """
80
81 for driver_modname,default_include in DRIVER_MODULES:
82     assert len([mod_name for mod_name,di in DRIVER_MODULES]) == 1, \
83         'Multiple %s entries in DRIVER_MODULES' % mod_name
84     this_mod = __import__(__name__, fromlist=[driver_modname])
85     driver_mod = getattr(this_mod, driver_modname)
86     for objname in dir(driver_mod):
87         obj = getattr(driver_mod, objname)
88         try:
89             subclass = issubclass(obj, Driver)
90         except TypeError:
91             continue
92         if subclass == True and obj != Driver:
93             d = obj()
94             if d.name != driver_modname:
95                 raise Exception('Driver name %s does not match module name %s'
96                                 % (d.name, driver_modname))
97             DRIVERS[d.name] = d
98
99 DRIVER_GRAPH = Graph([Node([DRIVERS[name] for name in d.dependencies()],
100                            data=d)
101                       for d in DRIVERS.values()])
102 DRIVER_GRAPH.topological_sort()
103
104
105 def default_settings():
106     settings = [Setting(
107             'drivers', help='Enable/disable default drivers.')]
108     for dnode in DRIVER_GRAPH:
109         driver = dnode.data
110         default_include = [di for mod_name,di in DRIVER_MODULES
111                            if mod_name == driver.name][0]
112         help = driver.__doc__.split('\n', 1)[0]
113         settings.append(Setting(
114                 section='drivers',
115                 option=driver.name,
116                 value=str(default_include),
117                 help=help,
118                 ))
119     for dnode in DRIVER_GRAPH:
120         driver = dnode.data
121         settings.extend(driver.default_settings())
122     return settings