[1078] | 1 | """Framsticks as a Python module.
|
---|
| 2 |
|
---|
| 3 | Static FramScript objects are available inside the module under their well known names
|
---|
| 4 | (frams.Simulator, frams.GenePools, etc.)
|
---|
| 5 |
|
---|
| 6 | These objects and all values passed to and from Framsticks are instances of frams.ExtValue.
|
---|
| 7 | Python values are automatically converted to Framstics data types.
|
---|
| 8 | Use frams.ExtValue._makeInt()/_makeDouble()/_makeString()/_makeNull() for explicit conversions.
|
---|
| 9 | Simple values returned from Framsticks can be converted to their natural Python
|
---|
| 10 | counterparts using _value() (or forced to a specific type with _int()/_double()/_string()).
|
---|
| 11 |
|
---|
| 12 | All non-Framsticks Python attributes start with '_' to avoid conflicts with Framsticks attributes.
|
---|
| 13 | Framsticks names that are Python reserved words are prefixed with 'x' (currently just Simulator.ximport).
|
---|
[1088] | 14 |
|
---|
| 15 | For sample usage, see frams-test.py and FramsticksLib.py.
|
---|
[1123] | 16 |
|
---|
| 17 | For interfaces in other languages (e.g. using the Framsticks library in your C++ code), see ../cpp/frams/frams-objects.h
|
---|
[1078] | 18 | """
|
---|
| 19 |
|
---|
| 20 | import ctypes, re, sys, os
|
---|
| 21 |
|
---|
| 22 | c_api = None # will be initialized in init(). Global because ExtValue uses it.
|
---|
| 23 |
|
---|
| 24 |
|
---|
| 25 | class ExtValue(object):
|
---|
[1083] | 26 | """All Framsticks objects and values are instances of this class. Read the documentation of the 'frams' module for more information."""
|
---|
[1081] | 27 |
|
---|
[1078] | 28 | _reInsideParens = re.compile('\((.*)\)')
|
---|
[1087] | 29 | _reservedWords = ['import'] # this list is scanned during every attribute access, only add what is really clashing with Framsticks properties
|
---|
[1078] | 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 ArgumentError("Can't make ExtValue from '" + str(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 |
|
---|
[1085] | 254 | def init(*args):
|
---|
[1083] | 255 | """
|
---|
| 256 | Initializes the connection to Framsticks dll/so.
|
---|
| 257 |
|
---|
[1087] | 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.
|
---|
[1085] | 262 |
|
---|
[1083] | 263 | """
|
---|
[1085] | 264 | # goals:
|
---|
[1087] | 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 = []
|
---|
[1085] | 270 | for a in args:
|
---|
[1087] | 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
|
---|
[1085] | 279 | else:
|
---|
| 280 | initargs.append(a)
|
---|
[1087] | 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 = '.'
|
---|
[1085] | 285 |
|
---|
[1078] | 286 | original_dir = os.getcwd()
|
---|
[1087] | 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
|
---|
[1085] | 289 | # for the hypothetical case without lib_path the abs_data must be obtained from somewhere else
|
---|
[1087] | 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 |
|
---|
[1085] | 298 | global c_api # access global variable
|
---|
[1087] | 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 './'
|
---|
[1088] | 301 | try:
|
---|
| 302 | c_api = ctypes.CDLL(lib_name)
|
---|
| 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
|
---|
[1106] | 306 | os.chdir(original_dir) # restore current working dir after loading the library so Framsticks sees the expected directory
|
---|
[1087] | 307 |
|
---|
[1078] | 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 |
|
---|
[1085] | 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)
|
---|
[1106] | 349 |
|
---|
[1078] | 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)
|
---|
[1106] | 357 |
|
---|
[1078] | 358 | print('Using Framsticks version: ' + str(Simulator.version_string))
|
---|
| 359 | print('Home (writable) dir : ' + home_dir)
|
---|
| 360 | print('Resources dir : ' + res_dir)
|
---|
| 361 | print()
|
---|