source: framspy/frams.py @ 1150

Last change on this file since 1150 was 1150, checked in by sz, 2 years ago

low level functions for getting more info about framsticks object properties (flags, full names, help texts, groups)

File size: 13.4 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
17If you want to run many independent instances of this class in parallel, use the "multiprocessing" module and then each process
18that uses this module will initialize it and get access to a separate instance of the Framsticks library.
19
20For interfaces in other languages (e.g. using the Framsticks library in your C++ code), see ../cpp/frams/frams-objects.h
21"""
22
23import ctypes, re, sys, os
24
25c_api = None  # will be initialized in init(). Global because ExtValue uses it.
26
27
28class ExtValue(object):
29        """All Framsticks objects and values are instances of this class. Read the documentation of the 'frams' module for more information."""
30
31        _reInsideParens = re.compile('\((.*)\)')
32        _reservedWords = ['import']  # this list is scanned during every attribute access, only add what is really clashing with Framsticks properties
33        _reservedXWords = ['x' + word for word in _reservedWords]
34        _encoding = 'utf-8'
35
36
37        def __init__(self, arg=None, dontinit=False):
38                if dontinit:
39                        return
40                if isinstance(arg, int):
41                        self._initFromInt(arg)
42                elif isinstance(arg, str):
43                        self._initFromString(arg)
44                elif isinstance(arg, float):
45                        self._initFromDouble(arg)
46                elif arg == None:
47                        self._initFromNull()
48                else:
49                        raise ctypes.ArgumentError("Can't make ExtValue from '%s' (%s)" % (str(arg), type(arg)))
50
51
52        def __del__(self):
53                c_api.extFree(self.__ptr)
54
55
56        def _initFromNull(self):
57                self.__ptr = c_api.extFromNull()
58
59
60        def _initFromInt(self, v):
61                self.__ptr = c_api.extFromInt(v)
62
63
64        def _initFromDouble(self, v):
65                self.__ptr = c_api.extFromDouble(v)
66
67
68        def _initFromString(self, v):
69                self.__ptr = c_api.extFromString(ExtValue._cstringFromPython(v))
70
71
72        @classmethod
73        def _makeNull(cls, v):
74                e = ExtValue(None, True)
75                e._initFromNull()
76                return e
77
78
79        @classmethod
80        def _makeInt(cls, v):
81                e = ExtValue(None, True)
82                e._initFromInt(v)
83                return e
84
85
86        @classmethod
87        def _makeDouble(cls, v):
88                e = ExtValue(None, True)
89                e._initFromDouble(v)
90                return e
91
92
93        @classmethod
94        def _makeString(cls, v):
95                e = ExtValue(None, True)
96                e._initFromString(v)
97                return e
98
99
100        @classmethod
101        def _rootObject(cls):
102                e = ExtValue(None, True)
103                e.__ptr = c_api.rootObject()
104                return e
105
106
107        @classmethod
108        def _stringFromC(cls, cptr):
109                return cptr.decode(ExtValue._encoding)
110
111
112        @classmethod
113        def _cstringFromPython(cls, s):
114                return ctypes.c_char_p(s.encode(ExtValue._encoding))
115
116
117        def _type(self):
118                return c_api.extType(self.__ptr)
119
120
121        def _class(self):
122                cls = c_api.extClass(self.__ptr)
123                if cls == None:
124                        return None
125                else:
126                        return ExtValue._stringFromC(cls)
127
128
129        def _value(self):
130                t = self._type()
131                if t == 0:
132                        return None
133                elif t == 1:
134                        return self._int()
135                elif t == 2:
136                        return self._double()
137                elif t == 3:
138                        return self._string()
139                else:
140                        return self
141
142
143        def _int(self):
144                return c_api.extIntValue(self.__ptr)
145
146
147        def _double(self):
148                return c_api.extDoubleValue(self.__ptr)
149
150
151        def _string(self):
152                return ExtValue._stringFromC(c_api.extStringValue(self.__ptr))
153
154
155        def _propCount(self):
156                return c_api.extPropCount(self.__ptr)
157
158        def _propId(self,i):
159                return ExtValue._stringFromC(c_api.extPropId(self.__ptr,i))
160
161        def _propName(self,i):
162                return ExtValue._stringFromC(c_api.extPropName(self.__ptr,i))
163
164        def _propType(self,i):
165                return ExtValue._stringFromC(c_api.extPropType(self.__ptr,i))
166
167        def _propHelp(self,i):
168                h=c_api.extPropHelp(self.__ptr,i) #unlike other string fields, help is sometimes NULL
169                return ExtValue._stringFromC(h) if h!=None else '';
170
171        def _propFlags(self,i):
172                return c_api.extPropFlags(self.__ptr,i)
173
174        def _propGroup(self,i):
175                return c_api.extPropGroup(self.__ptr,i)
176
177        def _groupCount(self):
178                return c_api.extGroupCount(self.__ptr)
179
180        def _groupName(self,i):
181                return ExtValue._stringFromC(c_api.extGroupName(self.__ptr,i))
182
183        def _groupMember(self,g,i):
184                return c_api.extGroupMember(self.__ptr,g,i)
185
186        def _memberCount(self,g):
187                return c_api.extMemberCount(self.__ptr,g)
188
189        def __str__(self):
190                return self._string()
191
192
193        def __dir__(self):
194                ids = dir(type(self))
195                if self._type() == 4:
196                        for i in range(c_api.extPropCount(self.__ptr)):
197                                name = ExtValue._stringFromC(c_api.extPropId(self.__ptr, i))
198                                if name in ExtValue._reservedWords:
199                                        name = 'x' + name
200                                ids.append(name)
201                return ids
202
203
204        def __getattr__(self, key):
205                if key[0] == '_':
206                        return self.__dict__[key]
207                if key in ExtValue._reservedXWords:
208                        key = key[1:]
209                prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key))
210                if prop_i < 0:
211                        raise AttributeError('no ' + str(key) + ' in ' + str(self))
212                t = ExtValue._stringFromC(c_api.extPropType(self.__ptr, prop_i))
213                if t[0] == 'p':
214                        arg_types = ExtValue._reInsideParens.search(t)
215                        if arg_types:
216                                arg_types = arg_types.group(1).split(',')  # anyone wants to add argument type validation using param type declarations?
217
218
219                        def fun(*args):
220                                ext_args = []
221                                ext_pointers = []
222                                for a in args:
223                                        if isinstance(a, ExtValue):
224                                                ext = a
225                                        else:
226                                                ext = ExtValue(a)
227                                        ext_args.append(ext)
228                                        ext_pointers.append(ext.__ptr)
229                                ret = ExtValue(None, True)
230                                args_array = (ctypes.c_void_p * len(args))(*ext_pointers)
231                                ret.__ptr = c_api.extPropCall(self.__ptr, prop_i, len(args), args_array)
232                                return ret
233
234
235                        return fun
236                else:
237                        ret = ExtValue(None, True)
238                        ret.__ptr = c_api.extPropGet(self.__ptr, prop_i)
239                        return ret
240
241
242        def __setattr__(self, key, value):
243                if key[0] == '_':
244                        self.__dict__[key] = value
245                else:
246                        if key in ExtValue._reservedXWords:
247                                key = key[1:]
248                        prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key))
249                        if prop_i < 0:
250                                raise AttributeError("No '" + str(key) + "' in '" + str(self) + "'")
251                        if not isinstance(value, ExtValue):
252                                value = ExtValue(value)
253                        c_api.extPropSet(self.__ptr, prop_i, value.__ptr)
254
255
256        def __getitem__(self, key):
257                return self.get(key)
258
259
260        def __setitem__(self, key, value):
261                return self.set(key, value)
262
263
264        def __len__(self):
265                try:
266                        return self.size._int()
267                except:
268                        return 0
269
270
271        def __iter__(self):
272                class It(object):
273                        def __init__(self, container, frams_it):
274                                self.container = container
275                                self.frams_it = frams_it
276
277
278                        def __iter__(self):
279                                return self
280
281
282                        def __next__(self):
283                                if self.frams_it.next._int() != 0:
284                                        return self.frams_it.value
285                                else:
286                                        raise StopIteration()
287
288                return It(self, self.iterator)
289
290
291def init(*args):
292        """
293        Initializes the connection to Framsticks dll/so.
294
295        Python programs do not have to know the Framstics path but if they know, just pass the path as the first argument.
296        Similarly '-dPATH' and '-DPATH' needed by Framsticks are optional and derived from the first path, unless they are specified as args in init().
297        '-LNAME' is the optional library name (full name including the file name extension), default is 'frams-objects.dll/.so' depending on the platform.
298        All other arguments are passed to Framsticks and not interpreted by this function.
299
300        """
301        # goals:
302        frams_d = None
303        frams_D = None
304        lib_path = None
305        lib_name = 'frams-objects.so' if os.name == 'posix' else 'frams-objects.dll'
306        initargs = []
307        for a in args:
308                if a[:2] == '-d':
309                        frams_d = a
310                elif a[:2] == '-D':
311                        frams_D = a
312                elif a[:2] == '-L':
313                        lib_name = a[2:]
314                elif lib_path is None:
315                        lib_path = a
316                else:
317                        initargs.append(a)
318        if lib_path is None:
319                # TODO: use environment variable and/or the zip distribution we are in when the path is not specified in arg
320                # for now just assume the current dir is Framsticks
321                lib_path = '.'
322
323        original_dir = os.getcwd()
324        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.
325        # TODO in python 3.8+ and Windows, use os.add_dll_directory(lib_path) instead of os.chdir()? And maybe for linux we no longer need chdir() and "./", but use absolute path?
326        abs_data = os.path.abspath('data')  # use absolute path for -d and -D so python is free to cd anywhere without confusing Framsticks
327        # for the hypothetical case without lib_path the abs_data must be obtained from somewhere else
328        if frams_d is None:
329                frams_d = '-d' + abs_data
330        if frams_D is None:
331                frams_D = '-D' + abs_data
332        initargs.insert(0, frams_d)
333        initargs.insert(0, frams_D)
334        initargs.insert(0, 'dummy.exe')  # as an offset, 0th arg is by convention app name
335
336        global c_api  # access global variable
337        if lib_path is not None and os.name == 'posix':
338                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 then load .so from some default system path without './'
339        try:
340                c_api = ctypes.CDLL(lib_name)  # if accessing this module from multiple threads, they will all share a single c_api and access the same copy of the library and its data. If you want separate independent copies, read the comment at the top of this file on using the "multiprocessing" module.
341        except OSError as e:
342                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()))
343                raise
344        os.chdir(original_dir)  # restore current working dir after loading the library so Framsticks sees the expected directory
345
346        c_api.init.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
347        c_api.init.restype = None
348        c_api.extFree.argtypes = [ctypes.c_void_p]
349        c_api.extFree.restype = None
350        c_api.extType.argtypes = [ctypes.c_void_p]
351        c_api.extType.restype = ctypes.c_int
352        c_api.extFromNull.argtypes = []
353        c_api.extFromNull.restype = ctypes.c_void_p
354        c_api.extFromInt.argtypes = [ctypes.c_int]
355        c_api.extFromInt.restype = ctypes.c_void_p
356        c_api.extFromDouble.argtypes = [ctypes.c_double]
357        c_api.extFromDouble.restype = ctypes.c_void_p
358        c_api.extFromString.argtypes = [ctypes.c_char_p]
359        c_api.extFromString.restype = ctypes.c_void_p
360        c_api.extIntValue.argtypes = [ctypes.c_void_p]
361        c_api.extIntValue.restype = ctypes.c_int
362        c_api.extDoubleValue.argtypes = [ctypes.c_void_p]
363        c_api.extDoubleValue.restype = ctypes.c_double
364        c_api.extStringValue.argtypes = [ctypes.c_void_p]
365        c_api.extStringValue.restype = ctypes.c_char_p
366        c_api.extClass.argtypes = [ctypes.c_void_p]
367        c_api.extClass.restype = ctypes.c_char_p
368        c_api.extPropCount.argtypes = [ctypes.c_void_p]
369        c_api.extPropCount.restype = ctypes.c_int
370        c_api.extPropId.argtypes = [ctypes.c_void_p, ctypes.c_int]
371        c_api.extPropId.restype = ctypes.c_char_p
372        c_api.extPropName.argtypes = [ctypes.c_void_p, ctypes.c_int]
373        c_api.extPropName.restype = ctypes.c_char_p
374        c_api.extPropType.argtypes = [ctypes.c_void_p, ctypes.c_int]
375        c_api.extPropType.restype = ctypes.c_char_p
376        c_api.extPropGroup.argtypes = [ctypes.c_void_p, ctypes.c_int]
377        c_api.extPropGroup.restype = ctypes.c_int
378        c_api.extPropFlags.argtypes = [ctypes.c_void_p, ctypes.c_int]
379        c_api.extPropFlags.restype = ctypes.c_int
380        c_api.extPropHelp.argtypes = [ctypes.c_void_p, ctypes.c_int]
381        c_api.extPropHelp.restype = ctypes.c_char_p
382        c_api.extPropFind.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
383        c_api.extPropFind.restype = ctypes.c_int
384        c_api.extPropGet.argtypes = [ctypes.c_void_p, ctypes.c_int]
385        c_api.extPropGet.restype = ctypes.c_void_p
386        c_api.extPropSet.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
387        c_api.extPropSet.restype = ctypes.c_int
388        c_api.extPropCall.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_void_p]
389        c_api.extPropCall.restype = ctypes.c_void_p
390        c_api.extGroupCount.argtypes = [ctypes.c_void_p]
391        c_api.extGroupCount.restype = ctypes.c_int
392        c_api.extGroupName.argtypes = [ctypes.c_void_p, ctypes.c_int]
393        c_api.extGroupName.restype = ctypes.c_char_p
394        c_api.extGroupMember.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
395        c_api.extGroupMember.restype = ctypes.c_int
396        c_api.extMemberCount.argtypes = [ctypes.c_void_p, ctypes.c_int]
397        c_api.extMemberCount.restype = ctypes.c_int
398        c_api.rootObject.argtypes = []
399        c_api.rootObject.restype = ctypes.c_void_p
400
401        c_args = (ctypes.c_char_p * len(initargs))(*list(a.encode(ExtValue._encoding) for a in initargs))
402        c_api.init(len(initargs), c_args)
403
404        Root = ExtValue._rootObject()
405        for n in dir(Root):
406                if n[0].isalpha():
407                        attr = getattr(Root, n)
408                        if isinstance(attr, ExtValue):
409                                attr = attr._value()
410                        setattr(sys.modules[__name__], n, attr)
411
412        print('Using Framsticks version: ' + str(Simulator.version_string))
413        print('Home (writable) dir     : ' + home_dir)
414        print('Resources dir           : ' + res_dir)
415        print()
Note: See TracBrowser for help on using the repository browser.