source: framspy/frams.py @ 1149

Last change on this file since 1149 was 1146, checked in by Maciej Komosinski, 3 years ago

Cosmetic

File size: 11.6 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 __str__(self):
156                return self._string()
157
158
159        def __dir__(self):
160                ids = dir(type(self))
161                if self._type() == 4:
162                        for i in range(c_api.extPropCount(self.__ptr)):
163                                name = ExtValue._stringFromC(c_api.extPropId(self.__ptr, i))
164                                if name in ExtValue._reservedWords:
165                                        name = 'x' + name
166                                ids.append(name)
167                return ids
168
169
170        def __getattr__(self, key):
171                if key[0] == '_':
172                        return self.__dict__[key]
173                if key in ExtValue._reservedXWords:
174                        key = key[1:]
175                prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key))
176                if prop_i < 0:
177                        raise AttributeError('no ' + str(key) + ' in ' + str(self))
178                t = ExtValue._stringFromC(c_api.extPropType(self.__ptr, prop_i))
179                if t[0] == 'p':
180                        arg_types = ExtValue._reInsideParens.search(t)
181                        if arg_types:
182                                arg_types = arg_types.group(1).split(',')  # anyone wants to add argument type validation using param type declarations?
183
184
185                        def fun(*args):
186                                ext_args = []
187                                ext_pointers = []
188                                for a in args:
189                                        if isinstance(a, ExtValue):
190                                                ext = a
191                                        else:
192                                                ext = ExtValue(a)
193                                        ext_args.append(ext)
194                                        ext_pointers.append(ext.__ptr)
195                                ret = ExtValue(None, True)
196                                args_array = (ctypes.c_void_p * len(args))(*ext_pointers)
197                                ret.__ptr = c_api.extPropCall(self.__ptr, prop_i, len(args), args_array)
198                                return ret
199
200
201                        return fun
202                else:
203                        ret = ExtValue(None, True)
204                        ret.__ptr = c_api.extPropGet(self.__ptr, prop_i)
205                        return ret
206
207
208        def __setattr__(self, key, value):
209                if key[0] == '_':
210                        self.__dict__[key] = value
211                else:
212                        if key in ExtValue._reservedXWords:
213                                key = key[1:]
214                        prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key))
215                        if prop_i < 0:
216                                raise AttributeError("No '" + str(key) + "' in '" + str(self) + "'")
217                        if not isinstance(value, ExtValue):
218                                value = ExtValue(value)
219                        c_api.extPropSet(self.__ptr, prop_i, value.__ptr)
220
221
222        def __getitem__(self, key):
223                return self.get(key)
224
225
226        def __setitem__(self, key, value):
227                return self.set(key, value)
228
229
230        def __len__(self):
231                try:
232                        return self.size._int()
233                except:
234                        return 0
235
236
237        def __iter__(self):
238                class It(object):
239                        def __init__(self, container, frams_it):
240                                self.container = container
241                                self.frams_it = frams_it
242
243
244                        def __iter__(self):
245                                return self
246
247
248                        def __next__(self):
249                                if self.frams_it.next._int() != 0:
250                                        return self.frams_it.value
251                                else:
252                                        raise StopIteration()
253
254                return It(self, self.iterator)
255
256
257def init(*args):
258        """
259        Initializes the connection to Framsticks dll/so.
260
261        Python programs do not have to know the Framstics path but if they know, just pass the path as the first argument.
262        Similarly '-dPATH' and '-DPATH' needed by Framsticks are optional and derived from the first path, unless they are specified as args in init().
263        '-LNAME' is the optional library name (full name including the file name extension), default is 'frams-objects.dll/.so' depending on the platform.
264        All other arguments are passed to Framsticks and not interpreted by this function.
265
266        """
267        # goals:
268        frams_d = None
269        frams_D = None
270        lib_path = None
271        lib_name = 'frams-objects.so' if os.name == 'posix' else 'frams-objects.dll'
272        initargs = []
273        for a in args:
274                if a[:2] == '-d':
275                        frams_d = a
276                elif a[:2] == '-D':
277                        frams_D = a
278                elif a[:2] == '-L':
279                        lib_name = a[2:]
280                elif lib_path is None:
281                        lib_path = a
282                else:
283                        initargs.append(a)
284        if lib_path is None:
285                # TODO: use environment variable and/or the zip distribution we are in when the path is not specified in arg
286                # for now just assume the current dir is Framsticks
287                lib_path = '.'
288
289        original_dir = os.getcwd()
290        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.
291        # 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?
292        abs_data = os.path.abspath('data')  # use absolute path for -d and -D so python is free to cd anywhere without confusing Framsticks
293        # for the hypothetical case without lib_path the abs_data must be obtained from somewhere else
294        if frams_d is None:
295                frams_d = '-d' + abs_data
296        if frams_D is None:
297                frams_D = '-D' + abs_data
298        initargs.insert(0, frams_d)
299        initargs.insert(0, frams_D)
300        initargs.insert(0, 'dummy.exe')  # as an offset, 0th arg is by convention app name
301
302        global c_api  # access global variable
303        if lib_path is not None and os.name == 'posix':
304                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 './'
305        try:
306                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.
307        except OSError as e:
308                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()))
309                raise
310        os.chdir(original_dir)  # restore current working dir after loading the library so Framsticks sees the expected directory
311
312        c_api.init.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
313        c_api.init.restype = None
314        c_api.extFree.argtypes = [ctypes.c_void_p]
315        c_api.extFree.restype = None
316        c_api.extType.argtypes = [ctypes.c_void_p]
317        c_api.extType.restype = ctypes.c_int
318        c_api.extFromNull.argtypes = []
319        c_api.extFromNull.restype = ctypes.c_void_p
320        c_api.extFromInt.argtypes = [ctypes.c_int]
321        c_api.extFromInt.restype = ctypes.c_void_p
322        c_api.extFromDouble.argtypes = [ctypes.c_double]
323        c_api.extFromDouble.restype = ctypes.c_void_p
324        c_api.extFromString.argtypes = [ctypes.c_char_p]
325        c_api.extFromString.restype = ctypes.c_void_p
326        c_api.extIntValue.argtypes = [ctypes.c_void_p]
327        c_api.extIntValue.restype = ctypes.c_int
328        c_api.extDoubleValue.argtypes = [ctypes.c_void_p]
329        c_api.extDoubleValue.restype = ctypes.c_double
330        c_api.extStringValue.argtypes = [ctypes.c_void_p]
331        c_api.extStringValue.restype = ctypes.c_char_p
332        c_api.extClass.argtypes = [ctypes.c_void_p]
333        c_api.extClass.restype = ctypes.c_char_p
334        c_api.extPropCount.argtypes = [ctypes.c_void_p]
335        c_api.extPropCount.restype = ctypes.c_int
336        c_api.extPropId.argtypes = [ctypes.c_void_p, ctypes.c_int]
337        c_api.extPropId.restype = ctypes.c_char_p
338        c_api.extPropType.argtypes = [ctypes.c_void_p, ctypes.c_int]
339        c_api.extPropType.restype = ctypes.c_char_p
340        c_api.extPropFind.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
341        c_api.extPropFind.restype = ctypes.c_int
342        c_api.extPropGet.argtypes = [ctypes.c_void_p, ctypes.c_int]
343        c_api.extPropGet.restype = ctypes.c_void_p
344        c_api.extPropSet.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
345        c_api.extPropSet.restype = ctypes.c_int
346        c_api.extPropCall.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_void_p]
347        c_api.extPropCall.restype = ctypes.c_void_p
348        c_api.rootObject.argtypes = []
349        c_api.rootObject.restype = ctypes.c_void_p
350
351        c_args = (ctypes.c_char_p * len(initargs))(*list(a.encode(ExtValue._encoding) for a in initargs))
352        c_api.init(len(initargs), c_args)
353
354        Root = ExtValue._rootObject()
355        for n in dir(Root):
356                if n[0].isalpha():
357                        attr = getattr(Root, n)
358                        if isinstance(attr, ExtValue):
359                                attr = attr._value()
360                        setattr(sys.modules[__name__], n, attr)
361
362        print('Using Framsticks version: ' + str(Simulator.version_string))
363        print('Home (writable) dir     : ' + home_dir)
364        print('Resources dir           : ' + res_dir)
365        print()
Note: See TracBrowser for help on using the repository browser.