source: framspy/frams.py @ 1170

Last change on this file since 1170 was 1167, checked in by Maciej Komosinski, 2 years ago

Cosmetic

File size: 13.5 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/dylib.
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.dylib' if sys.platform == 'darwin' else '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        if os.name == 'nt':
324                if sys.version_info < (3, 8):
325                        original_dir = os.getcwd()
326                        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.
327                else:
328                        os.add_dll_directory(lib_path)
329        abs_data = os.path.join(os.path.abspath(lib_path), "data")  # use absolute path for -d and -D so python is free to cd anywhere without confusing Framsticks
330
331
332        # for the hypothetical case without lib_path the abs_data must be obtained from somewhere else
333        if frams_d is None:
334                frams_d = '-d' + abs_data
335        if frams_D is None:
336                frams_D = '-D' + abs_data
337        initargs.insert(0, frams_d)
338        initargs.insert(0, frams_D)
339        initargs.insert(0, 'dummy.exe')  # as an offset, 0th arg is by convention app name
340
341        global c_api  # access global variable
342        if lib_path is not None and os.name == 'posix':
343                lib_name = os.path.join(lib_path, lib_name)  # lib_path is always set ('.' when not specified). For the (currently unused) case of lib_path==None, the resulting lib_name is a bare filename without any path, which loads the library from a system-configured location.
344        try:
345                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.
346        except OSError as e:
347                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()))
348                raise
349
350        if os.name == 'nt' and sys.version_info < (3, 8):
351                os.chdir(original_dir)  # restore current working dir after loading the library so Framsticks sees the expected directory
352
353        c_api.init.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
354        c_api.init.restype = None
355        c_api.extFree.argtypes = [ctypes.c_void_p]
356        c_api.extFree.restype = None
357        c_api.extType.argtypes = [ctypes.c_void_p]
358        c_api.extType.restype = ctypes.c_int
359        c_api.extFromNull.argtypes = []
360        c_api.extFromNull.restype = ctypes.c_void_p
361        c_api.extFromInt.argtypes = [ctypes.c_int]
362        c_api.extFromInt.restype = ctypes.c_void_p
363        c_api.extFromDouble.argtypes = [ctypes.c_double]
364        c_api.extFromDouble.restype = ctypes.c_void_p
365        c_api.extFromString.argtypes = [ctypes.c_char_p]
366        c_api.extFromString.restype = ctypes.c_void_p
367        c_api.extIntValue.argtypes = [ctypes.c_void_p]
368        c_api.extIntValue.restype = ctypes.c_int
369        c_api.extDoubleValue.argtypes = [ctypes.c_void_p]
370        c_api.extDoubleValue.restype = ctypes.c_double
371        c_api.extStringValue.argtypes = [ctypes.c_void_p]
372        c_api.extStringValue.restype = ctypes.c_char_p
373        c_api.extClass.argtypes = [ctypes.c_void_p]
374        c_api.extClass.restype = ctypes.c_char_p
375        c_api.extPropCount.argtypes = [ctypes.c_void_p]
376        c_api.extPropCount.restype = ctypes.c_int
377        c_api.extPropId.argtypes = [ctypes.c_void_p, ctypes.c_int]
378        c_api.extPropId.restype = ctypes.c_char_p
379        c_api.extPropName.argtypes = [ctypes.c_void_p, ctypes.c_int]
380        c_api.extPropName.restype = ctypes.c_char_p
381        c_api.extPropType.argtypes = [ctypes.c_void_p, ctypes.c_int]
382        c_api.extPropType.restype = ctypes.c_char_p
383        c_api.extPropGroup.argtypes = [ctypes.c_void_p, ctypes.c_int]
384        c_api.extPropGroup.restype = ctypes.c_int
385        c_api.extPropFlags.argtypes = [ctypes.c_void_p, ctypes.c_int]
386        c_api.extPropFlags.restype = ctypes.c_int
387        c_api.extPropHelp.argtypes = [ctypes.c_void_p, ctypes.c_int]
388        c_api.extPropHelp.restype = ctypes.c_char_p
389        c_api.extPropFind.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
390        c_api.extPropFind.restype = ctypes.c_int
391        c_api.extPropGet.argtypes = [ctypes.c_void_p, ctypes.c_int]
392        c_api.extPropGet.restype = ctypes.c_void_p
393        c_api.extPropSet.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
394        c_api.extPropSet.restype = ctypes.c_int
395        c_api.extPropCall.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_void_p]
396        c_api.extPropCall.restype = ctypes.c_void_p
397        c_api.extGroupCount.argtypes = [ctypes.c_void_p]
398        c_api.extGroupCount.restype = ctypes.c_int
399        c_api.extGroupName.argtypes = [ctypes.c_void_p, ctypes.c_int]
400        c_api.extGroupName.restype = ctypes.c_char_p
401        c_api.extGroupMember.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
402        c_api.extGroupMember.restype = ctypes.c_int
403        c_api.extMemberCount.argtypes = [ctypes.c_void_p, ctypes.c_int]
404        c_api.extMemberCount.restype = ctypes.c_int
405        c_api.rootObject.argtypes = []
406        c_api.rootObject.restype = ctypes.c_void_p
407
408        c_args = (ctypes.c_char_p * len(initargs))(*list(a.encode(ExtValue._encoding) for a in initargs))
409        c_api.init(len(initargs), c_args)
410
411        Root = ExtValue._rootObject()
412        for n in dir(Root):
413                if n[0].isalpha():
414                        attr = getattr(Root, n)
415                        if isinstance(attr, ExtValue):
416                                attr = attr._value()
417                        setattr(sys.modules[__name__], n, attr)
418
419        print('Using Framsticks version: ' + str(Simulator.version_string))
420        print('Home (writable) dir     : ' + home_dir)
421        print('Resources dir           : ' + res_dir)
422        print()
Note: See TracBrowser for help on using the repository browser.