modelverse.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. import urllib
  2. import urllib2
  3. import json
  4. import random
  5. from urllib2 import URLError
  6. import sys
  7. COMPILER_PATH = "interface/HUTN"
  8. # Bind to the compiler (might have to update path manually!)
  9. sys.path.append(COMPILER_PATH)
  10. from hutn_compiler.compiler import main as do_compile
  11. # Exceptions
  12. class ModelverseException(Exception):
  13. pass
  14. class UnknownException(ModelverseException):
  15. pass
  16. class UnknownIdentifier(ModelverseException):
  17. pass
  18. class UnknownType(ModelverseException):
  19. pass
  20. class NotAnAssociation(ModelverseException):
  21. pass
  22. class UnsupportedValue(ModelverseException):
  23. pass
  24. class CompilationError(ModelverseException):
  25. pass
  26. class NoSuchAttribute(ModelverseException):
  27. pass
  28. class UnknownModel(ModelverseException):
  29. pass
  30. class ConnectionError(ModelverseException):
  31. pass
  32. class ModelExists(ModelverseException):
  33. pass
  34. class PermissionDenied(ModelverseException):
  35. pass
  36. class InvalidMode(ModelverseException):
  37. pass
  38. class InterfaceMismatch(ModelverseException):
  39. pass
  40. # Helper functions and configuration: do not use yourself!
  41. taskname = random.random()
  42. address = None
  43. last_output = None
  44. mode = 0
  45. def _input(value):
  46. # Ugly json encoding of primitives
  47. if isinstance(value, type([])):
  48. value = json.dumps(value)
  49. urllib2.urlopen(urllib2.Request(address, urllib.urlencode({"op": "set_input", "data": value, "taskname": taskname}))).read()
  50. else:
  51. value = json.dumps(value)
  52. #print("Set input: " + str(value))
  53. urllib2.urlopen(urllib2.Request(address, urllib.urlencode({"op": "set_input", "value": value, "taskname": taskname}))).read()
  54. def _input_raw(value, taskname):
  55. # Ugly json encoding of primitives
  56. urllib2.urlopen(urllib2.Request(address, urllib.urlencode({"op": "set_input", "value": value, "taskname": taskname}))).read()
  57. def _compile_AL(code):
  58. # Compile an action language file and send the compiled code
  59. code_fragments = code.split("\n")
  60. code_fragments = [i for i in code_fragments if i.strip() != ""]
  61. code_fragments = [i.replace(" ", "\t") for i in code_fragments]
  62. initial_tabs = min([len(i) - len(i.lstrip("\t")) for i in code_fragments])
  63. code_fragments = [i[initial_tabs:] for i in code_fragments]
  64. code_fragments.append("")
  65. code = "\n".join(code_fragments)
  66. with open("__constraint.alc", "w") as f:
  67. f.write(code)
  68. f.flush()
  69. return do_compile("__constraint.alc", COMPILER_PATH + "/grammars/actionlanguage.g", "CS")
  70. def _compile_model(code):
  71. # Compile a model and send the compiled graph
  72. # First change multiple spaces to a tab
  73. code_fragments = code.split("\n")
  74. code_fragments = [i for i in code_fragments if i.strip() != ""]
  75. code_fragments = [i.replace(" ", "\t") for i in code_fragments]
  76. initial_tabs = min([len(i) - len(i.lstrip("\t")) for i in code_fragments])
  77. code_fragments = [i[initial_tabs:] for i in code_fragments]
  78. code_fragments.append("")
  79. code = "\n".join(code_fragments)
  80. with open("__model.mvc", "w") as f:
  81. f.write(code)
  82. f.flush()
  83. return do_compile("__model.mvc", COMPILER_PATH + "/grammars/modelling.g", "M") + ["exit"]
  84. def _output(expected=None):
  85. try:
  86. global last_output
  87. last_output = json.loads(urllib2.urlopen(urllib2.Request(address, urllib.urlencode({"op": "get_output", "taskname": taskname}))).read())
  88. #print("Got output: " + str(last_output))
  89. except:
  90. raise UnknownError()
  91. if expected is not None and last_output != expected:
  92. raise InterfaceMismatch(_last_output(), expected)
  93. return last_output
  94. def _last_output():
  95. return last_output
  96. # Raise common exceptions
  97. def _handle_output(requested=None, split=None):
  98. value = _output()
  99. if value.startswith("Model exists: "):
  100. raise ModelExists(value.split(": ", 1)[1])
  101. elif value.startswith("Permission denied"):
  102. raise PermissionDenied(value.split(": ", 1)[1])
  103. elif value.startswith("Model not found: "):
  104. raise UnknownModel(value.split(": ", 1)[1])
  105. elif value.startswith("Element not found: "):
  106. raise UnknownIdentifier(value.split(": ", 1)[1])
  107. elif value.startswith("Element exists: "):
  108. raise ElementExists(value.split(": ", 1)[1])
  109. elif value.startswith("Attribute not found: "):
  110. raise NoSuchAttribute(value.split(": ", 1)[1])
  111. elif requested is not None and value.startswith(requested):
  112. if split is None:
  113. return value
  114. else:
  115. splitted = value.strip().split(split, 1)
  116. if len(splitted) == 1:
  117. return ""
  118. else:
  119. return splitted[1].rstrip()
  120. else:
  121. raise InterfaceMismatch(value)
  122. # Main MvC operations
  123. def init(address_param="http://127.0.0.1:8001"):
  124. """Starts up the connection to the Modelverse."""
  125. # return None
  126. # raises ConnectionError
  127. # raises UnknownError
  128. # raises InvalidMode
  129. global mode
  130. if mode != 0:
  131. raise InvalidMode()
  132. global address
  133. address = address_param
  134. try:
  135. _input_raw('"%s"' % taskname, "task_manager")
  136. mode = 1
  137. except URLError as e:
  138. raise ConnectionError(e.reason)
  139. def login(username, password):
  140. """Log in a user, if user doesn't exist, it is created."""
  141. # return None
  142. # raises UnknownError
  143. # raises PermissionDenied
  144. # raises InterfaceMismatch
  145. global mode
  146. if mode != 1:
  147. raise InvalidMode()
  148. _output("Log on as which user?")
  149. _input(username)
  150. if _output() == "Password for existing user?":
  151. _input(password)
  152. if _output() == "Welcome to the Model Management Interface v2.0!":
  153. _output("Use the 'help' command for a list of possible commands")
  154. _input("quiet")
  155. mode = 2
  156. elif _last_output() == "Wrong password!":
  157. raise PermissionDenied()
  158. else:
  159. raise InterfaceMismatch(_last_output())
  160. elif _last_output() == "This is a new user: please give password!":
  161. _input(password)
  162. _output("Please repeat the password")
  163. _input(password)
  164. if _output() == "Passwords match!":
  165. _output("Welcome to the Model Management Interface v2.0!")
  166. _output("Use the 'help' command for a list of possible commands")
  167. _input("quiet")
  168. mode = 2
  169. elif _last_output() == "Not the same password!":
  170. # We just sent the same password, so it should be identical, unless the interface changed
  171. raise InterfaceMismatch(_last_output())
  172. else:
  173. raise InterfaceMismatch(_last_output())
  174. else:
  175. raise InterfaceMismatch(_last_output())
  176. def model_add(model_name, metamodel_name, model_code=None):
  177. """Instantiate a new model."""
  178. # return None
  179. # raises UnknownModel
  180. # raises ModelExists
  181. # raises UnknownError
  182. # raises PermissionDenied
  183. # raises CompilationError
  184. if mode != 2:
  185. raise InvalidMode()
  186. # Do this before creating the model, as otherwise compilation errors would make us inconsistent
  187. if model_code is not None:
  188. try:
  189. compiled = _compile_model(model_code)
  190. except:
  191. raise CompilationError()
  192. else:
  193. compiled = ["exit"]
  194. _input(["model_add", metamodel_name, model_name])
  195. _handle_output("Waiting for model constructors...")
  196. _input(compiled)
  197. _output("Success")
  198. def model_modify(model_name):
  199. """Modify an existing model."""
  200. # return is_write
  201. # raises UnknownModel
  202. # raises PermissionDenied
  203. # raises UnknownError
  204. global mode
  205. if mode != 2:
  206. raise InvalidMode()
  207. _input(["model_modify", model_name])
  208. _handle_output("Success")
  209. # Mode has changed
  210. mode = 3
  211. _output("Model loaded, ready for commands!")
  212. if ("r/w" in _output()):
  213. write = True
  214. else:
  215. write = False
  216. _output("Use 'help' command for a list of possible commands")
  217. return write
  218. def model_list():
  219. """List all models."""
  220. # return [(model1, metamodel1), (model2, metamodel2), ...]
  221. # raises UnknownError
  222. if mode != 2:
  223. raise InvalidMode()
  224. _input("model_list")
  225. output = _handle_output("Success: ", split=" ")
  226. if output == "":
  227. return []
  228. lst = []
  229. value = output.strip().split("\n")
  230. for v in value:
  231. m, mm = v.split(":")
  232. m = m.strip()
  233. mm = mm.strip()
  234. lst.append((m, mm))
  235. return lst
  236. def model_list_full():
  237. """List full information on all models."""
  238. # return [(model1, metamodel1, owner1, group1, permissions1), (model2, metamodel2, owner2, group2, permissions2), ...]
  239. # raises UnknownError
  240. if mode != 2:
  241. raise InvalidMode()
  242. _input("model_list_full")
  243. lst = []
  244. value = _output().strip().split("\n")
  245. for v in value:
  246. m, mm = v.split(":")
  247. m = m.strip()
  248. mm = mm.strip()
  249. perm, own, grp, m = m.split(" ")
  250. lst.append((m, mm, own, grp, perm))
  251. return lst
  252. def verify(model):
  253. """Verify if a model conforms to its metamodel."""
  254. # return "verification_result"
  255. # raises UnknownError
  256. # raises UnknownModel
  257. if mode != 2:
  258. raise InvalidMode()
  259. _input(["verify", model])
  260. return _handle_output("Success: ", split=" ")
  261. def model_overwrite(model_name, new_model=None):
  262. """Upload a new model and overwrite an existing model."""
  263. # return None
  264. # raises UnknownModel
  265. # raises PermissionDenied
  266. # raises CompilationError
  267. # raises UnknownError
  268. if mode != 2:
  269. raise InvalidMode()
  270. if new_model is not None:
  271. try:
  272. compiled = _compile_model(new_model)
  273. except Exception as e:
  274. raise CompilationError(e)
  275. else:
  276. compiled = ["exit"]
  277. _input(["model_overwrite", model_name])
  278. _handle_output("Waiting for model constructors...")
  279. _input(compiled)
  280. _output("Success")
  281. def user_logout():
  282. """Log out the current user and break the connection."""
  283. # return None
  284. # raises UnknownException
  285. global mode
  286. if mode != 2:
  287. raise InvalidMode()
  288. _input("exit")
  289. mode = 0
  290. def user_delete():
  291. """Removes the current user and break the connection."""
  292. # return None
  293. # raises UnknownException
  294. global mode
  295. if mode != 2:
  296. raise InvalidMode()
  297. _input("self-destruct")
  298. mode = 0
  299. def model_render(model, mapper):
  300. """Fetch a rendered verion of a model."""
  301. # return JSON_representation
  302. # raises UnknownException
  303. # raises UnknownIdentifier
  304. # raises InterfaceMismatch
  305. global mode
  306. if mode != 2:
  307. raise InvalidMode()
  308. _input(["model_render", model, mapper])
  309. return _handle_output("Success: ", split=" ")
  310. def transformation_between(source, target):
  311. global mode
  312. if mode != 2:
  313. raise InvalidMode()
  314. _input(["transformation_between", source, target])
  315. output = _handle_output("Success: ", split=" ")
  316. if output == "":
  317. return []
  318. lst = [v for v in output.split("\n")]
  319. def transformation_add_MT_language():
  320. """Create a new Model Transformation language out of a set of metamodels."""
  321. raise NotImplementedError()
  322. def transformation_add_MT():
  323. """Create a new model transformation."""
  324. raise NotImplementedError()
  325. def transformation_add_AL(source_metamodels, target_metamodels, operation_name, code):
  326. """Create a new action language model, which can be executed."""
  327. global mode
  328. if mode != 2:
  329. raise InvalidMode()
  330. try:
  331. compiled = _compile_AL(code)
  332. except Exception as e:
  333. raise CompilationError(e)
  334. _input(["transformation_add_AL"] + source_metamodels + [""] + target_metamodels + [""] + [operation_name])
  335. _handle_output("Waiting for code constructors...")
  336. _input(compiled)
  337. _output("Success")
  338. def transformation_add_MANUAL():
  339. """Create a new manual model operation."""
  340. raise NotImplementedError()
  341. def transformation_execute_AL(operation_name, input_models_dict, output_models_dict, callback=lambda i: None):
  342. """Execute an existing model operation."""
  343. global mode
  344. if mode != 2:
  345. raise InvalidMode()
  346. mv_dict_rep = []
  347. for key, value in input_models_dict.items():
  348. mv_dict_rep += [key, value]
  349. mv_dict_rep += [""]
  350. for key, value in output_models_dict.items():
  351. mv_dict_rep += [key, value]
  352. mv_dict_rep += [""]
  353. _input(["transformation_execute", operation_name] + mv_dict_rep)
  354. _handle_output("Success: ready for AL execution")
  355. # We are now executing, so everything we get is part of the dialog, except if it is the string for transformation termination
  356. while _output() not in ["Success", "Failure"]:
  357. reply = callback(_last_output())
  358. if reply is not None:
  359. _input(reply)
  360. # Got termination message, so we are done!
  361. if _last_output() == "Success":
  362. return True
  363. else:
  364. return False
  365. def transformation_list():
  366. """List existing model operations."""
  367. raise NotImplementedError()
  368. def transformation_list_full():
  369. """List detailed information on model operations."""
  370. raise NotImplementedError()
  371. def transformation_detail():
  372. """List full details of a a model operation."""
  373. raise NotImplementedError()
  374. def transformation_RAMify():
  375. """Ramify an existing metamodel."""
  376. raise NotImplementedError()
  377. def process_execute():
  378. """Execute a process model."""
  379. raise NotImplementedError()
  380. def permission_modify():
  381. """Modify permissions of a model."""
  382. raise NotImplementedError()
  383. def permission_owner():
  384. """Modify the owning user of a model."""
  385. raise NotImplementedError()
  386. def permission_group():
  387. """Modify the owning group of a model."""
  388. raise NotImplementedError()
  389. def group_create():
  390. """Create a new group."""
  391. raise NotImplementedError()
  392. def group_delete():
  393. """Delete a group of which you are an owner."""
  394. raise NotImplementedError()
  395. def group_owner_add():
  396. """Add a new owning user to a group you own."""
  397. raise NotImplementedError()
  398. def group_owner_delete():
  399. """Delete an owning user to a group you own."""
  400. raise NotImplementedError()
  401. def group_join():
  402. """Add a new user to a group you own."""
  403. raise NotImplementedError()
  404. def group_kick():
  405. """Delete a user from a group you own."""
  406. raise NotImplementedError()
  407. def group_list():
  408. """List existing groups."""
  409. raise NotImplementedError()
  410. def admin_promote():
  411. """Promote a user to admin status."""
  412. raise NotImplementedError()
  413. def admin_demote():
  414. """Demote a user from admin status."""
  415. raise NotImplementedError()
  416. # Actual operations on the model
  417. def element_list(model_name):
  418. """Return a list of all IDs and the type of the element"""
  419. # return [(name1, type1), (name2, type2), ...]
  420. # raises UnknownError
  421. model_modify(model_name)
  422. if mode != 3:
  423. raise InvalidMode()
  424. try:
  425. _input("list_full")
  426. lst = []
  427. output = _handle_output("Success: ", split=" ")
  428. if output == "":
  429. return []
  430. for v in output.split("\n"):
  431. m, mm = v.split(":")
  432. m = m.strip()
  433. mm = mm.strip()
  434. lst.append((m, mm))
  435. return lst
  436. finally:
  437. model_exit()
  438. def types(model_name):
  439. """Return a list of all types usable in the model"""
  440. # return [type1, type2, ...]
  441. # raises UnknownError
  442. model_modify(model_name)
  443. if mode != 3:
  444. raise InvalidMode()
  445. try:
  446. _input("types")
  447. lst = []
  448. output = _handle_output("Success: ", split=" ")
  449. if output == "":
  450. return []
  451. for v in output.split("\n"):
  452. m, mm = v.split(":")
  453. m = m.strip()
  454. lst.append(m)
  455. return lst
  456. finally:
  457. model_exit()
  458. def types_full(model_name):
  459. """Return a list of full types usable in the model"""
  460. # return [(type1, typetype1), (type2, typetype2), ...]
  461. # raises UnknownError
  462. model_modify(model_name)
  463. if mode != 3:
  464. raise InvalidMode()
  465. try:
  466. _input("types")
  467. lst = []
  468. output = _handle_output("Success: ", split=" ")
  469. if output == "":
  470. return []
  471. for v in output.split("\n"):
  472. m, mm = v.split(":")
  473. m = m.strip()
  474. mm = mm.strip()
  475. lst.append((m, mm))
  476. return lst
  477. finally:
  478. model_exit()
  479. def read(model_name, ID):
  480. """Return a tuple of information on the element: its type and source/target (None if not an edge)"""
  481. # return (type, (source, target))
  482. # raises UnknownError
  483. # raises UnknownIdentifier
  484. model_modify(model_name)
  485. if mode != 3:
  486. raise InvalidMode()
  487. try:
  488. _input(["read", ID])
  489. output = _handle_output("Success: ", split=" ")
  490. v = output.split("\n")
  491. t = v[1].split(":")[1].strip()
  492. if (not v[0].startswith("Source:")):
  493. rval = (t, None)
  494. else:
  495. src = v[0].split(":")[1].strip()
  496. trg = v[1].split(":")[1].strip()
  497. rval = (t, (src, trg))
  498. return rval
  499. finally:
  500. model_exit()
  501. def read_attrs(model_name, ID):
  502. """Return a dictionary of attribute value pairs"""
  503. # return {attr1: value1, attr2: value2, ...}
  504. # raises UnknownError
  505. # raises UnknownIdentifier
  506. model_modify(model_name)
  507. if mode != 3:
  508. raise InvalidMode()
  509. try:
  510. _input(["read", ID])
  511. output = _handle_output("Success: ", split=" ")
  512. v = output.split("\n")
  513. searching = True
  514. rval = {}
  515. for r in v:
  516. if searching:
  517. if r == "Attributes:":
  518. # Start working on attributes
  519. searching = False
  520. else:
  521. key, value = r.split(":", 1)
  522. _, value = value.split("=", 1)
  523. key = json.loads(key.strip())
  524. value = value.strip()
  525. if value == "None":
  526. value = None
  527. elif value == "True":
  528. value = True
  529. elif value == "False":
  530. value = False
  531. else:
  532. value = json.loads(value)
  533. rval[key] = value
  534. return rval
  535. finally:
  536. model_exit()
  537. def instantiate(model_name, typename, edge=None):
  538. """Create a new instance of the specified typename, between the selected elements (if not None), and with the provided ID (if any)"""
  539. # return instantiated_ID
  540. # raises UnknownError
  541. # raises UnknownType
  542. # raises UnknownIdentifier
  543. # raises NotAnEdge
  544. model_modify(model_name)
  545. if mode != 3:
  546. raise InvalidMode()
  547. try:
  548. if edge is None:
  549. _input(["instantiate_node", typename, ""])
  550. else:
  551. _input(["instantiate_edge", typename, "", edge[0], edge[1]])
  552. return _handle_output("Success: ", split=" ")
  553. finally:
  554. model_exit()
  555. def delete_element(model_name, ID):
  556. """Delete the element with the given ID"""
  557. # return None
  558. # raises UnknownError
  559. # raises UnknownIdentifier
  560. model_modify(model_name)
  561. if mode != 3:
  562. raise InvalidMode()
  563. try:
  564. _input(["delete", ID])
  565. _handle_output("Success")
  566. finally:
  567. model_exit()
  568. def attr_assign(model_name, ID, attr, value):
  569. """Assign a value to an attribute"""
  570. # return None
  571. # raises UnknownError
  572. # raises UnknownIdentifier
  573. # raises NoSuchAttribute
  574. # raises UnsupportedValue
  575. model_modify(model_name)
  576. if mode != 3:
  577. raise InvalidMode()
  578. try:
  579. _input(["attr_add", ID, attr, value])
  580. _handle_output("Success")
  581. finally:
  582. model_exit()
  583. def attr_assign_code(model_name, ID, attr, code):
  584. """Assign a piece of Action Language code to the attribute"""
  585. # return None
  586. # raises UnknownError
  587. # raises UnknownIdentifier
  588. # raises NoSuchAttribute
  589. # raises UnsupportedValue
  590. try:
  591. compiled = _compile_AL(code)
  592. except Exception as e:
  593. raise CompilationError(e)
  594. model_modify(model_name)
  595. if mode != 3:
  596. raise InvalidMode()
  597. try:
  598. _input(["attr_add", ID, attr])
  599. _handle_output("Waiting for code constructors...")
  600. _input(compiled)
  601. _output("Success")
  602. finally:
  603. model_exit()
  604. def attr_delete(model_name, ID, attr):
  605. """Remove an attribute."""
  606. model_modify(model_name)
  607. if mode != 3:
  608. raise InvalidMode()
  609. try:
  610. _input(["attr_del", ID, attr])
  611. _handle_output("Success")
  612. finally:
  613. model_exit()
  614. def read_outgoing(model_name, ID, typename):
  615. """Returns a list of all outgoing associations of a specific type ("" = all)"""
  616. # return [name1, name2, ...]
  617. # raises UnknownError
  618. # raises UnknownIdentifier
  619. model_modify(model_name)
  620. if mode != 3:
  621. raise InvalidMode()
  622. try:
  623. _input(["read_outgoing", ID, typename])
  624. output = _handle_output("Success: ", split=" ")
  625. if output == "":
  626. return []
  627. else:
  628. return output.split("\n")
  629. finally:
  630. model_exit()
  631. def read_incoming(model_name, ID, typename):
  632. """Returns a list of all incoming associations of a specific type ("" = all)"""
  633. # return [name1, name2, ...]
  634. # raises UnknownError
  635. # raises UnknownIdentifier
  636. # raises UnknownType
  637. model_modify(model_name)
  638. if mode != 3:
  639. raise InvalidMode()
  640. try:
  641. _input(["read_incoming", ID, typename])
  642. output = _handle_output("Success: ", split=" ")
  643. if output == "":
  644. return []
  645. else:
  646. return output.split("\n")
  647. finally:
  648. model_exit()
  649. def read_association_source(model_name, ID):
  650. """Returns the source of an association."""
  651. # returns name
  652. # raises UnknownError
  653. # raises UnknownIdentifier
  654. # raises NotAnAssociation
  655. model_modify(model_name)
  656. if mode != 3:
  657. raise InvalidMode()
  658. try:
  659. _input(["read_association_source", ID])
  660. return _handle_output("Success: ", split=" ")
  661. finally:
  662. model_exit()
  663. def read_association_destination(model_name, ID):
  664. """Returns the destination of an association."""
  665. # returns name
  666. # raises UnknownError
  667. # raises UnknownIdentifier
  668. # raises NotAnAssociation
  669. model_modify(model_name)
  670. if mode != 3:
  671. raise InvalidMode()
  672. try:
  673. _input(["read_association_destination", ID])
  674. return _handle_output("Success: ", split=" ")
  675. finally:
  676. model_exit()
  677. def model_exit():
  678. """Leave model modify mode."""
  679. # return None
  680. # raises UnknownError
  681. global mode
  682. if mode != 3:
  683. raise InvalidMode()
  684. _input("exit")
  685. _output("Success")
  686. mode = 2