source: framspy/frams.py @ 1142

Last change on this file since 1142 was 1141, checked in by Maciej Komosinski, 4 years ago

More useful exception message - shows argument type

File size: 11.0 KB
Line 
1"""Framsticks as a Python module.
2
3Static FramScript objects are available inside the module under their well known names
4(frams.Simulator, frams.GenePools, etc.)
5
6These objects and all values passed to and from Framsticks are instances of frams.ExtValue.
7Python values are automatically converted to Framstics data types.
8Use frams.ExtValue._makeInt()/_makeDouble()/_makeString()/_makeNull() for explicit conversions.
9Simple values returned from Framsticks can be converted to their natural Python
10counterparts using _value() (or forced to a specific type with  _int()/_double()/_string()).
11
12All non-Framsticks Python attributes start with '_' to avoid conflicts with Framsticks attributes.
13Framsticks names that are Python reserved words are prefixed with 'x' (currently just Simulator.ximport).
14
15For sample usage, see frams-test.py and FramsticksLib.py.
16
17For interfaces in other languages (e.g. using the Framsticks library in your C++ code), see ../cpp/frams/frams-objects.h
18"""
19
20import ctypes, re, sys, os
21
22c_api = None  # will be initialized in init(). Global because ExtValue uses it.
23
24
25class ExtValue(object):
26        """All Framsticks objects and values are instances of this class. Read the documentation of the 'frams' module for more information."""
27
28        _reInsideParens = re.compile('\((.*)\)')
29        _reservedWords = ['import']  # this list is scanned during every attribute access, only add what is really clashing with Framsticks properties
30        _reservedXWords = ['x' + word for word in _reservedWords]
31        _encoding = 'utf-8'
32
33
34        def __init__(self, arg=None, dontinit=False):
35                if dontinit:
36                        return
37                if isinstance(arg, int):
38                        self._initFromInt(arg)
39                elif isinstance(arg, str):
40                        self._initFromString(arg)
41                elif isinstance(arg, float):
42                        self._initFromDouble(arg)
43                elif arg == None:
44                        self._initFromNull()
45                else:
46                        raise ctypes.ArgumentError("Can't make ExtValue from '%s' (%s)" % (str(arg), type(arg)))
47
48
49        def __del__(self):
50                c_api.extFree(self.__ptr)
51
52
53        def _initFromNull(self):
54                self.__ptr = c_api.extFromNull()
55
56
57        def _initFromInt(self, v):
58                self.__ptr = c_api.extFromInt(v)
59
60
61        def _initFromDouble(self, v):
62                self.__ptr = c_api.extFromDouble(v)
63
64
65        def _initFromString(self, v):
66                self.__ptr = c_api.extFromString(ExtValue._cstringFromPython(v))
67
68
69        @classmethod
70        def _makeNull(cls, v):
71                e = ExtValue(None, True)
72                e._initFromNull()
73                return e
74
75
76        @classmethod
77        def _makeInt(cls, v):
78                e = ExtValue(None, True)
79                e._initFromInt(v)
80                return e
81
82
83        @classmethod
84        def _makeDouble(cls, v):
85                e = ExtValue(None, True)
86                e._initFromDouble(v)
87                return e
88
89
90        @classmethod
91        def _makeString(cls, v):
92                e = ExtValue(None, True)
93                e._initFromString(v)
94                return e
95
96
97        @classmethod
98        def _rootObject(cls):
99                e = ExtValue(None, True)
100                e.__ptr = c_api.rootObject()
101                return e
102
103
104        @classmethod
105        def _stringFromC(cls, cptr):
106                return cptr.decode(ExtValue._encoding)
107
108
109        @classmethod
110        def _cstringFromPython(cls, s):
111                return ctypes.c_char_p(s.encode(ExtValue._encoding))
112
113
114        def _type(self):
115                return c_api.extType(self.__ptr)
116
117
118        def _class(self):
119                cls = c_api.extClass(self.__ptr)
120                if cls == None:
121                        return None
122                else:
123                        return ExtValue._stringFromC(cls)
124
125
126        def _value(self):
127                t = self._type()
128                if t == 0:
129                        return None
130                elif t == 1:
131                        return self._int()
132                elif t == 2:
133                        return self._double()
134                elif t == 3:
135                        return self._string()
136                else:
137                        return self
138
139
140        def _int(self):
141                return c_api.extIntValue(self.__ptr)
142
143
144        def _double(self):
145                return c_api.extDoubleValue(self.__ptr)
146
147
148        def _string(self):
149                return ExtValue._stringFromC(c_api.extStringValue(self.__ptr))
150
151
152        def __str__(self):
153                return self._string()
154
155
156        def __dir__(self):
157                ids = dir(type(self))
158                if self._type() == 4:
159                        for i in range(c_api.extPropCount(self.__ptr)):
160                                name = ExtValue._stringFromC(c_api.extPropId(self.__ptr, i))
161                                if name in ExtValue._reservedWords:
162                                        name = 'x' + name
163                                ids.append(name)
164                return ids
165
166
167        def __getattr__(self, key):
168                if key[0] == '_':
169                        return self.__dict__[key]
170                if key in ExtValue._reservedXWords:
171                        key = key[1:]
172                prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key))
173                if prop_i < 0:
174                        raise AttributeError('no ' + str(key) + ' in ' + str(self))
175                t = ExtValue._stringFromC(c_api.extPropType(self.__ptr, prop_i))
176                if t[0] == 'p':
177                        arg_types = ExtValue._reInsideParens.search(t)
178                        if arg_types:
179                                arg_types = arg_types.group(1).split(',')  # anyone wants to add argument type validation using param type declarations?
180
181
182                        def fun(*args):
183                                ext_args = []
184                                ext_pointers = []
185                                for a in args:
186                                        if isinstance(a, ExtValue):
187                                                ext = a
188                                        else:
189                                                ext = ExtValue(a)
190                                        ext_args.append(ext)
191                                        ext_pointers.append(ext.__ptr)
192                                ret = ExtValue(None, True)
193                                args_array = (ctypes.c_void_p * len(args))(*ext_pointers)
194                                ret.__ptr = c_api.extPropCall(self.__ptr, prop_i, len(args), args_array)
195                                return ret
196
197
198                        return fun
199                else:
200                        ret = ExtValue(None, True)
201                        ret.__ptr = c_api.extPropGet(self.__ptr, prop_i)
202                        return ret
203
204
205        def __setattr__(self, key, value):
206                if key[0] == '_':
207                        self.__dict__[key] = value
208                else:
209                        if key in ExtValue._reservedXWords:
210                                key = key[1:]
211                        prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key))
212                        if prop_i < 0:
213                                raise AttributeError("No '" + str(key) + "' in '" + str(self) + "'")
214                        if not isinstance(value, ExtValue):
215                                value = ExtValue(value)
216                        c_api.extPropSet(self.__ptr, prop_i, value.__ptr)
217
218
219        def __getitem__(self, key):
220                return self.get(key)
221
222
223        def __setitem__(self, key, value):
224                return self.set(key, value)
225
226
227        def __len__(self):
228                try:
229                        return self.size._int()
230                except:
231                        return 0
232
233
234        def __iter__(self):
235                class It(object):
236                        def __init__(self, container, frams_it):
237                                self.container = container
238                                self.frams_it = frams_it
239
240
241                        def __iter__(self):
242                                return self
243
244
245                        def __next__(self):
246                                if self.frams_it.next._int() != 0:
247                                        return self.frams_it.value
248                                else:
249                                        raise StopIteration()
250
251                return It(self, self.iterator)
252
253
254def init(*args):
255        """
256        Initializes the connection to Framsticks dll/so.
257
258        Python programs do not have to know the Framstics path but if they know, just pass the path as the first argument.
259        Similarly '-dPATH' and '-DPATH' needed by Framsticks are optional and derived from the first path, unless they are specified as args in init().
260        '-LNAME' is the optional library name (full name including the file name extension), default is 'frams-objects.dll/.so' depending on the platform.
261        All other arguments are passed to Framsticks and not interpreted by this function.
262
263        """
264        # goals:
265        frams_d = None
266        frams_D = None
267        lib_path = None
268        lib_name = 'frams-objects.so' if os.name == 'posix' else 'frams-objects.dll'
269        initargs = []
270        for a in args:
271                if a[:2] == '-d':
272                        frams_d = a
273                elif a[:2] == '-D':
274                        frams_D = a
275                elif a[:2] == '-L':
276                        lib_name = a[2:]
277                elif lib_path is None:
278                        lib_path = a
279                else:
280                        initargs.append(a)
281        if lib_path is None:
282                # TODO: use environment variable and/or the zip distribution we are in when the path is not specified in arg
283                # for now just assume the current dir is Framsticks
284                lib_path = '.'
285
286        original_dir = os.getcwd()
287        os.chdir(lib_path)  # because under Windows, frams-objects.dll requires other dll's which reside in the same directory, so we must change current dir for them to be found while loading the main dll
288        abs_data = os.path.abspath('data')  # use absolute path for -d and -D so python is free to cd anywhere without confusing Framsticks
289        # for the hypothetical case without lib_path the abs_data must be obtained from somewhere else
290        if frams_d is None:
291                frams_d = '-d' + abs_data
292        if frams_D is None:
293                frams_D = '-D' + abs_data
294        initargs.insert(0, frams_d)
295        initargs.insert(0, frams_D)
296        initargs.insert(0, 'dummy.exe')  # as an offset, 0th arg is by convention app name
297
298        global c_api  # access global variable
299        if lib_path is not None and os.name == 'posix':
300                lib_name = './' + lib_name  # currently we always have lib_path (even if it is incorrect) but hypothetically it could work with lib_path==None and load .so from some default system path without './'
301        try:
302                c_api = ctypes.CDLL(lib_name)  # should be a separate instance and no globals if we want multiple threads to use this module (todo?)
303        except OSError as e:
304                print("*** Could not find or open '%s' from '%s'.\n*** Did you provide proper arguments and is this file readable?\n" % (lib_name, os.getcwd()))
305                raise
306        os.chdir(original_dir)  # restore current working dir after loading the library so Framsticks sees the expected directory
307
308        c_api.init.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
309        c_api.init.restype = None
310        c_api.extFree.argtypes = [ctypes.c_void_p]
311        c_api.extFree.restype = None
312        c_api.extType.argtypes = [ctypes.c_void_p]
313        c_api.extType.restype = ctypes.c_int
314        c_api.extFromNull.argtypes = []
315        c_api.extFromNull.restype = ctypes.c_void_p
316        c_api.extFromInt.argtypes = [ctypes.c_int]
317        c_api.extFromInt.restype = ctypes.c_void_p
318        c_api.extFromDouble.argtypes = [ctypes.c_double]
319        c_api.extFromDouble.restype = ctypes.c_void_p
320        c_api.extFromString.argtypes = [ctypes.c_char_p]
321        c_api.extFromString.restype = ctypes.c_void_p
322        c_api.extIntValue.argtypes = [ctypes.c_void_p]
323        c_api.extIntValue.restype = ctypes.c_int
324        c_api.extDoubleValue.argtypes = [ctypes.c_void_p]
325        c_api.extDoubleValue.restype = ctypes.c_double
326        c_api.extStringValue.argtypes = [ctypes.c_void_p]
327        c_api.extStringValue.restype = ctypes.c_char_p
328        c_api.extClass.argtypes = [ctypes.c_void_p]
329        c_api.extClass.restype = ctypes.c_char_p
330        c_api.extPropCount.argtypes = [ctypes.c_void_p]
331        c_api.extPropCount.restype = ctypes.c_int
332        c_api.extPropId.argtypes = [ctypes.c_void_p, ctypes.c_int]
333        c_api.extPropId.restype = ctypes.c_char_p
334        c_api.extPropType.argtypes = [ctypes.c_void_p, ctypes.c_int]
335        c_api.extPropType.restype = ctypes.c_char_p
336        c_api.extPropFind.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
337        c_api.extPropFind.restype = ctypes.c_int
338        c_api.extPropGet.argtypes = [ctypes.c_void_p, ctypes.c_int]
339        c_api.extPropGet.restype = ctypes.c_void_p
340        c_api.extPropSet.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
341        c_api.extPropSet.restype = ctypes.c_int
342        c_api.extPropCall.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_void_p]
343        c_api.extPropCall.restype = ctypes.c_void_p
344        c_api.rootObject.argtypes = []
345        c_api.rootObject.restype = ctypes.c_void_p
346
347        c_args = (ctypes.c_char_p * len(initargs))(*list(a.encode(ExtValue._encoding) for a in initargs))
348        c_api.init(len(initargs), c_args)
349
350        Root = ExtValue._rootObject()
351        for n in dir(Root):
352                if n[0].isalpha():
353                        attr = getattr(Root, n)
354                        if isinstance(attr, ExtValue):
355                                attr = attr._value()
356                        setattr(sys.modules[__name__], n, attr)
357
358        print('Using Framsticks version: ' + str(Simulator.version_string))
359        print('Home (writable) dir     : ' + home_dir)
360        print('Resources dir           : ' + res_dir)
361        print()
Note: See TracBrowser for help on using the repository browser.