Wrappers ======== Several wrappers can be defined for the Modelverse, as the Modelverse is merely a service running externally. To communicate effectively, and automatically, a programming language wrapper is recommended. Nonetheless, it is possible to communicatie manually as well. These are some of the implemented wrappers. Prompt ------ The simplest wrapper is the prompt wrapper, which merely sends the input and output directly to the user. This wrapper has almost no code, but requires users to manually decide which content to send next. It has no built-in integration with any model or action language compilers. Nonetheless, it is an easy way to test out the raw communication protocol manually. Python ------ The first real wrapper is the Python-based wrapper. It provides a set of functions for use by Python code. These functions wrap not only the interface, but also provides simple error handling through the use of Python exceptions and contains the model and action language compilers. An overview of all functions and associatied exceptions is provided below. All operations happen *synchronously*, meaning that they block until the Modelverse has performed the requested operation. Note that some functions are only applicable in a certain *context*. In practice, this means that you should first issue the *init* and *login* operations, as otherwise your connection with the Modelverse will not have started up yet. For each function, we provide an example to indicate how this operation can be used. In these first examples, assume that all referenced elements are present, all permissions are granted, etc. For each exception, then, we provide an example where it can occur. In these examples, the problem is often caused by an non-existing element or unsatisfied permissions. From the example, it should be clear what the problem is. Functions ^^^^^^^^^ .. function:: init(address_param="http://127.0.0.1:8001", timeout=20.0) Start up the connection to the Modelverse, residing at *address_param*. This connection is an XML/HTTPRequest and will start up a new task at the Modelverse. Retries for *timeout* seconds until giving up. The timeout includes all HTTP errors, and will therefore keep retrying even on failed attempts. As this request is synchronous, like all others, it will block until a connection has been established. Examples: * To create a connection to a local Modelverse. >>> init() Done * To create a connection to the Modelverse running remotely. >>> init("http://modelverse.uantwerpen.be:8001") .. function:: login(username, password) Explanation Logs in the currently active Modelverse connection to the specified *username* and *password*. If the user does not exist, it will create a new user with the specified password. If the user already exists, it will try to log in with the provided password. Examples: * To login as user user1, with the specified password. >>> login("user1", "my_password") .. function:: model_add(model_name, metamodel_name, model_code=None) Explanation Upload a new model that can later be referred to as *model_name*, conforming to *metamodel_name*. The model itself is stored in the string *model_code*. This string is parsed using the HUTN compiler and subsequently sent to the Modelverse. When *model_code* is empty, an empty model is created. Examples: * To create a new model called PetriNets, conforming to SimpleClassDiagrams, and load the model stored in models/PetriNets.mvc. >>> model_add("formalisms/PetriNets", "formalisms/SimpleClassDiagrams", open("models/PetriNets.mvc", "r").read()) * To create a minimal instance of the language afterwards, which only contains a single place (and no attributes). >>> model_add("models/my_pn", "formalisms/PetriNets", "Place p1 {}") * To create a less minimal instance of the language, stored in models/my_pn2.mvc. >>> model_add("models/my_pn2", "formalisms/PetriNets", open("models/my_pn2.mvc", "r").read()) .. function:: upload_code(code) Upload a string of *code* in the Action Language formalism. This piece of code is compiled with the HUTN compiler and sent to the Modelverse directly. Makes the assumption that the **construct_function()** operation is currently running on the Modelverse, as otherwise the data will be misinterpreted. This is normally only useful in model transformations, where you want to upload a piece of code on-the-fly (e.g., adding a breakpoint in Action Language). Examples: * To upload the file models/code.alc. >>> upload_code(open("models/code.alc", "r").read()) * To upload a code fragment inline. >>> upload_code("Void function a():\nreturn!") .. function:: model_delete(model_name) Delete the model referred to by name *model_name*. This is a non-cascading delete, with almost no checks: model transformations depending on this model will likely become corrupted. Examples: * To delete a previously created model. >>> model_delete("models/my_pn2") * Or to delete a metamodel, which is itself just a model. >>> model_delete("formalisms/PetriNets") * To delete a full folder. >>> model_delete("formalisms") .. function:: model_list(location) Returns a list of all models existing in the specified folder. Sub-folders can be recognized because they have a trailing forward slash in their name. Examples: * To get a list of all models in the formalisms directory. >>> model_list("formalisms") ["PetriNets", "SimpleClassDiagrams", "Bottom", "Tracability", ...] .. function:: model_list_full(location) Returns a detailed list of all models existing in the specified folder in the Modelverse. This list includes information on permissions, owner, and group. Examples: * To get a detailed list of all currently present models. >>> model_list_full("models") [("my_pn", "user1", "users", "200"), ("my_pn2", "user1", "users", "200"), ...] .. function:: verify(model_name, metamodel_name) Verify whether *model_name* conforms to *metammodel_name*, as both stored in the Modelverse. Returns either "OK" if the model conforms, or a string specifying the reason for non-conformance. Examples: * Verifying a conforming model. >>> verify("formalisms/PetriNets", "formalisms/SimpleClassDiagrams") OK * Or verify using the alternative conformance relation (conformance bottom). >>> verify("formalisms/PetriNets", "formalisms/Bottom") OK * Verifying a non-conforming model. >>> verify("models/my_pn") Lower cardinality violation for attribute "name" at Place p1. .. function:: model_overwrite(model_name, new_model_code=None) Overwrites the model previously known under the name *model_name* with the model code in *new_model_code*. This operation differs from first deleting the model and then recreating it, as all metadata of the model is kept, such as access permissions. The new model can be kept empty, in which case the model will be cleared. Examples: * To overwrite the PetriNets metamodel with a newer version, thereby also updating the metamodel of all existing instances ("my_pn" and "my_pn2"). >>> model_overwrite("formalisms/PetriNets", open("models/PetriNets2.mvc", "r").read()) * To overwrite an existing PetriNets instance. >>> model_overwrite("models/my_pn", """Place p2 {}""") .. function:: user_logout() Logs out the current user, thereby closing the task. Subsequent operations will no longer have any effect, as the task was terminated. To log in as a different user, the *init* operation has to be executed again. Examples: * To log out the current user and allow future logins by this user. >>> user_logout() .. function:: user_delete() Delete the current user and thereafter log out. This removes the current user, making it impossible to log in as this user again. Existing models tied to this user, such as those the user is an owner of, remain bound to the (removed) user. While it is possible to recreate a new user with the same name, model permissions will not be inherited to this new user with the same name. Examples: * To delete the current user, and make all owned models owner-less. >>> user_delete() .. function:: model_render(model_name, mapper_name) Render the model by name of *model_name* using the mapper by name of *mapper_name*. Both parameters have to be known models in the Modelverse. Outputs a JSON representation of the rendered model. This is basically just a shortcut for executing the specified operation and reading out the resulting model with a JSON representation. Examples: * To render the PetriNets instance using the PetriNetsMapper. >>> model_render("models/my_pn", "formalisms/PetriNetsMapper") [{"id": "__12345", "type": "Ellipse", "x": 100, "y": 150, "height": 20, "width: "20"}] .. function:: transformation_between(source, target) List all transformations that originate at *source* and end at *target*. Transformations can still be selected, even if they take more source models than those specified in the parameters. Examples: * To fetch all endogenous transformations on PetriNets, assuming that some were previously defined. >>> transformation_between("formalisms/PetriNets", "formalisms/PetriNets") ["PN_simulate", "PN_optimize"] * To fetch all transformations from a DSL to PetriNets, assuming that multiple people created different denotational semantics. >>> transformation_between("RPGame", "PetriNets") ["denotational1", "denotational_2", "denotational_3"] .. function:: transformation_add_MT(source_metamodels, target_metamodels, operation_name, code, callback=lambda: None) Create a new model transformation operation. The new transformation takes *source_metamodels* as input, and generates *target_metamodels* as output. Both parameters are dictionaries of the form {name: metamodel_name}. The name is used later on in the model transformation as a prefix to the type. A single metamodel_name can be used for multiple names. Note that the target metamodel names may overlap with the source metamodel names, but the metamodel type should be identical. The operation is henceforth known by *operation_name* and is provided as a model in the string *code*. Optionally, a callback is defined which performs some operations on the merged metamodel, for example to define tracability links between (previously unrelated) metamodels. In the background, this operation does all necessary RAMification and model merging. Examples: * To create a new model transformation for PetriNets simulation. >>> transformation_add_MT({"pn": "formalisms/PetriNets"}, {"pn": "formalisms/PetriNets"}, "models/pn_simulate", open("models/PN_simulate.mvc", "r").read()) * To create a model transformation from a DSL to PetriNets, which requires tracability links. >>> def tracability_links(): ... instantiate("Association", ID="Tile2Place", ("dsl/Tile", "pn/Place")) ... instantiate("Association", ID="Dirrection2Transition", ("dsl/Direction", "pn/Transition")) >>> transformation_add_MT({"dsl": "formalisms/RPGame"}, {"pn": "formalisms/PetriNets"}, "models/denotational_1", open("models/denotational_1.mvc", "r").read(), tracability_links) * To create a multi-input model transformation. >>> transformation_add_MT({"pn_1": "formalisms/PetriNets", "pn_2": "formalisms/PetriNets", "architecture: "formalisms/Architecture"}, {"result": "formalisms/PetriNets"}, "models/PN_merge", open("models/PN_merge.mvc", "r").read()) .. function:: transformation_add_AL(source_metamodels, target_metamodels, operation_name, code, callback=lambda: None) Creates a new action language operation. Similar to *transformation_add_MT*, but now does not require RAMification. The *code* parameter also is not specified as a Modelverse model (.mvc), but as action language (.alc). Examples: * To create a new action language operation for PetriNets reachability analysis. >>> transformation_add_AL({"pn": "formalisms/PetriNets"}, {"graph": "formalisms/ReachabilityGraph"}, "models/pn_analyze", open("models/PN_reachability.alc", "r").read()) * To create an action language operation from a Scheduling DSL to a list, which requires tracability links. >>> def tracability_links(): ... instantiate("Association", ID="Task2Event", ("schedule/Task", "list/Event")) >>> transformation_add_AL({"schedule": "formalisms/SchedulingDSL"}, {"list": "formalisms/EventList"}, "models/sequentialize", open("models/sequentialize_schedule.alc", "r").read(), tracability_links) .. function:: transformation_add_MANUAL(source_metamodels, target_metamodels, operation_name, callback=lambda: None) Creates a new manual operation. Identical to *transformation_add_AL*, but does not take any code as content. Examples: * To create a manual refinement operation on PetriNets. >>> transformation_add_MANUAL({"pn": "formalisms/PetriNets"}, {"pn": "formalisms/PetriNets"}, "models/pn_refine") * To create a multi-input refinement operation on PetriNets. >>> transformation_add_MANUAL({"pn": "formalisms/PetriNets", "requirements": "formalisms/Requirements"}, {"pn": "formalisms/PetriNets"}, "models/pn_refine_req") .. function:: transformation_execute_AL(operation_name, input_models_dict, output_models_dict, callback=lambda i: None) Executes the Action Language model *operation_name* with *input_models_dict* as inputs and *output_models_dict* as outputs. For both dicts, the contents describe the mapping between the parameter names of the operation to the names in the Modelverse. Values in *input_models_dict* must be existing models, whereas values in *output_models_dict* can be non-existing upon invocation. A *callback* function can be defined when the action language model requires user input or output. This callback function can be used to communicate with the executing action language directly. Examples: * To execute reachability analysis on an existing petri net. >>> transformation_execute_AL("models/pn_analyze", {"pn": "models/my_pn"}, {"graph": "models/my_pn_reachability"}) * To execute reachability analysis which prompts the user, for example because it is a debugging prompt. >>> def callback(value): ... print(value) # Prints out the prompt of the execution of the Action Language fragment ... return raw_input() # Sends a raw request from the user to the Modelverse, consumed in the Action Language >>> transformation_execute_AL("models/pn_simulate", {"pn": "models/my_pn"}, {"graph": "models/my_pn_reachability"}, callback) .. function:: transformation_execute_MANUAL(operation_name, input_models_dict, output_models_dict, callback=lambda i: None) Executes the manual model operation *operation_name*. Furthermore, this is identical to *transformation_execute_AL*, with the exception of the *callback* function. In this case, the callback function can be just another series of Modelverse operations, though pertaining to a single model. As such, the *model_name* parameter of these operations **MUST** be set to *None*. Examples: * To execute a manual operation, which requires you to refine a PetriNets instance. >>> def callback(): ... p1 = instantiate(None, "pn/Place") ... t1 = instantiate(None, "pn/Transition") ... instantiate(None, "pn/P2T", (p1, t1)) >>> transformation_execute_MANUAL("models/pn_refine", {"pn": "models/my_pn"}, {"pn": "models/my_pn"}, callback) .. function:: transformation_execute_MT(operation_name, input_models_dict, output_models_dict, callback=lambda i: None) Executes the model transformation operation *operation_name*. Identical to *transformation_execute_AL*. Examples: * To execute a model transformation on a PetriNets instance, thereby putting the result in a different model. >>> transformation_execute_MT("models/pn_simulate", {"pn": "models/my_pn"}, {"pn": "models/my_simulated_pn"}) * To execute a model transformation which prompts the user. >>> def callback(value): ... print(value) ... return raw_input() >>> transformation_execute_MT("models/pn_simulate_prompt", {"pn": "models/my_pn"}, {"pn": "models/my_simulated_pn"}, callback) .. function:: process_execute(process_name, prefix, callbacks) Execute the process model stored as *process_name*. All models are stored with their names prefixed with *prefix* to allow for multiple executions of the same model. Note that this applies to the resolution of input models as well. Optionally, a dictionary of *callbacks* can be defined with as key the operation that is being executed, and value the actual callback. The callbacks must be similarly defined just like how they were defined in the individual *transformation_execute* operations. Examples: * To execute a process model for the power window example. >>> process_execute("models/pm_powerwindow", "pw_") * To execute a process model for the power window example, which requires user input for some operations. >>> def refine_architecture(): ... # Do some operation on the architecture model here ... node1 = instantiate(None, "Node") ... node2 = instantiate(None, "Node") ... instantiate(None, "Connection", (node1, node2)) >>> def refine_control(): ... # Do some operation on the control model here ... s1 = instantiate(None, "State") ... s2 = instantiate(None, "State") ... instantiate(None, "Transition", (s1, s2)) >>> def refine_query(): ... # Do some operation on the safety query model here ... p1 = instantiate(None, "Place") ... attr_assign(None, p1, "tokens", 2) >>> process_execute("models/pm_powerwindow", "pw_", {"models/refine_plant": refine_plant, "models/refine_control": refine_control, "models/refine_query": refine_query}) .. function:: permission_modify(model_name, permissions) Change the permissions of *model_name* to *permissions*. The permissions is a string of three characters, each between 0 and 2. The format is similar to the UNIX permission system: the leftmost character is the permission for the owning user, the middle character for members of the ownin group, and the rightmost character for all other users. Character 0 signifies no access, 1 read-only access, and 2 full read/write access. Note that changing permissions trickle down to the instances as well: if the metamodel is no longer readable to the user, all models conforming to it become unreadable in this specific context. Examples: * To modify the permissions of the PetriNets metamodel, allowing only the owner to read and write to it. >>> permission_modify("formalisms/PetriNets", "200") * To modify the permissions of a PetriNets model, granting everyone read/write access. >>> permission_modify("formalisms/PetriNets", "222") .. function:: permission_owner(model_name, owner) Change the owner of the model *model_name* to *owner*. Changing permissions trickles down to the instances as well: if the metamodel is no longer readable to the user, all models conforming to it become unreadable in this specific context. Examples: * To change the owning user of the PetriNets metamodel to user2. >>> permission_owner("formalisms/PetriNets", "user2") .. function:: permission_group(model_name, group) Change the owning group of the model *model_name* to *group*. The same remarks hold as for all other permission operations. Examples: * To change the owning group of the PetriNets metamodel to group1. >>> permission_group("formalisms/PetriNets", "group1") .. function:: group_create(group_name) Create a new group named *group_name*. You automatically become an administrator for this group. Examples: * To create a new group called group2. >>> group_create("group2") .. function:: group_delete(group_name) Delete the group named *group_name*. All users will automatically be kicked from the group, and all permissions previously granted by this group become void. Examples: * To delete the group group2. >>> group_delete("group2") .. function:: group_owner_add(group_name, user_name) Add user *user_name* as an owner of group *group_name*. This automatically makes the user join the specified group if this was not yet the case. Examples: * To add user user1 as a new owner, or group administrator, to group group1. >>> group_owner_add("group1", "user1") .. function:: group_owner_delete(group_name, user_name) Remove user *user_name* as an owner of group *group_name*. Examples: * To delete user1 as an owner, or group administrator, from group group1. >>> group_owner_delete("group1", "user1") .. function:: group_join(group_name, user_name) Have user *user_name* join the group *group_name* as an ordinary user. Examples: * To make user user1 join the group group1, of which the current user is an owner. >>> group_join("group1", "user1") .. function:: group_kick(group_name, user_name) Remove user *user_name* from the group *group_name*. If the user was an owner of the group, these permissions are also revoked. Examples: * To kick user user1 from group group1, of which the current user is an owner. >>> group_kick("group1, "user1") .. function:: admin_promote(user_name) Promote user *user_name* to admin status. Admin status grants users access to all operations, and provides all permission. Effectively, all groups and files have read/write access for the admin user, independent of the stored permissions. Examples: * To promote user1 to admin states. >>> admin_promote("user1") .. function:: admin_demote(user_name) Demote user *user_name* to ordinary user. Examples: * To demote user1 to a normal user. >>> admin_demote("user1") .. function:: element_list(model_name) Returns a list of all elements and their type specified in the model named *model_name*. This list can contain much more than only simple elements, but includes anonymous edges and attributes as well. It is therefore not recommended for general purpose use; use *element_list_nice* instead. Examples: * To get a list of all elements in the PetriNets metamodel. >>> element_list("formalisms/PetriNets") [("Place", "Class"), ("Transition", "Class"), ("P2T", "Association"), ("T2P", "Association"), ...] .. function:: types(model_name) Returns a list of all types usable in the model named *model_name*. This is similar to executing *element_list* on the metamodel of this model. It attempts to filter out most unusable elements. Examples: * To get a list of all types usable in the PetriNets metamodel (i.e., when altering the metamodel itself). >>> types("formalisms/PetriNets") ["Class", "Association", "SimpleAttribute", ...] .. function:: types_full(model_name) Returns a list of all types usable in the model named *model_name*. In contrast to *types*, this includes hidden elements as well (i.e., those starting with __) Examples: * To get a list of all types usable in the PetriNets metamodel (i.e., when altering the metamodel itself). >>> types("formalisms/PetriNets") ["Class", "Association", "SimpleAttribute", "__12345", ...] .. function:: read(model_name, ID) Read the content of model element *ID* in model *model_name*. This returns the type of the element, and the set of source and destination if the element is an edge (*None* otherwise). Examples: * To read out the P2T link in the PetriNets metamodel. >>> read("formalisms/PetriNets", "P2T") ["Association", ("Place", "Transition")] * To read out the Place node in the PetriNets metamodel. >>> read("formalisms/PetriNets", "Place") ["Class", None] * To read out some P2T instance in a PetriNets model. >>> read("my_pn", "p1_to_t1") ["P2T", ("p1", "t1")] * To read out some Place instance in a PetriNets model. >>> read("my_pn", "p1") ["Place", None] .. function:: read_attrs(model_name, ID) Return a dictionary of all attributes of model element *ID* in model *model_name*, containing their values. All values in the Modelverse are primitive types, and as such, this is also the case in this operation. The value is *None* in case the attribute is not set. Examples: * To read out the attributes of the Place class. >>> read_attrs("formalisms/PetriNets", "Place") {"lower_cardinality": None, "upper_cardinality": None} * To read out the attributes of a Place instance. >>> read_attrs("models/my_pn", "p1") {"name": "critical_section", "tokens": 1} .. function:: instantiate(model_name, typename, edge=None, ID="") Instantiate a new instance of *typename* in the model *model_name*. If the instance is an edge, provide a tuple containing the source and target as *edge*. A preferred *ID* can be specified, though there is no guarantee that this name is actually used (e.g., if it is already taken by another element). This operation returns the actually assigned ID. It is this ID that is used for all other operations on the model. Examples: * To create a new Place instance in a PetriNets model. >>> instantiate("models/my_pn", "Place") "__12345" * To create a new Place instance with a preferred ID, which is granted. >>> instantiate("models/my_pn", "Place", ID="critical_section") "critical_section" * To create a new Place instance with a preferred ID, which is not granted. >>> instantiate("models/my_pn", "Place", ID="critical_section") critical_section_12345" * To create a new P2T instance in a PetriNets model. >>> instantiate("models/my_pn", "P2T", ("p1", "t1")) "__12345" * To create a new concept in the PetriNets metamodel, which can later on be used in all instances immediately. >>> instantiate("formalisms/PetriNets", "Association", ("Place", "Transition"), ID="InhibitorArc") "InhibitorArc" >>> instantiate("models/my_pn", "InhibitorArc", ("p1", "t1")) "__12345" .. function:: delete_element(model_name, ID) Delete the element *ID* in the model *model_name*. This is a recursive delete, and all incoming and outgoing edges will be removed (recursively) as well. Examples: * To delete an existing element in a PetriNets model. >>> delete_element("models/my_pn", "critical_section") * To delete an existing exdge in a PetriNets model. >>> delete_element("models/my_pn", "p1_to_t1") * When deleting an element "p1", the arc "p1_to_t1" is also removed automatically. >>> delete_element("models/my_pn", "p1") >>> delete_element("models/my_pn", "p1_to_t1") UnknownIdentifierException("p1_to_t1") .. function:: attr_assign(model_name, ID, attr, value) Assign the value *value* to the attribute named *attr* of the element *ID* in the model named *model_name*. If the attribute already has an assigned value, the previous value is removed first. Examples: * To assign some attributes to a Place instance. >>> attr_assign("models/my_pn", "p1", "name", "my first place") >>> attr_assign("models/my_pn", "p1", "tokens", 1) * To assign some attributes to the Place class itself. >>> attr_assign("formalisms/PetriNets", "Place", "upper_cardinality", 1) .. function:: attr_assign_code(model_name, ID, attr, code) Assign the code block *code* to the attribute named *attr* of the element *ID* in the model named *model_name*. If the attribute already has an assigned value, the previous value is removed first. The assigned code is compiled to Action Language by the HUTN compiler. Examples: * To assign a piece of action code to a Statecharts transition, loaded from file. >>> attr_assign_code("models/my_sc", "t1", "script", open("models/t1_script", "r").read()) * To assign a piece of action code to a Statecharts transition, defined inline. >>> code = \ ... """ ... Void function action(attributes : Element): ... dict_overwrite(attributes, "counter", 1) ... return! ... """ >>> attr_assign_code("models/my_sc", "t1", "script", code) .. function:: attr_delete(model_name, ID, attr) Unset the attribute *attr* of model element *ID* in the model *model_name*. This is not necessary when assigning a new value, as *attr_assign* automatically does this when required. As such, this only is useful when dealing with an optional attribute, or when you want to create a non-conforming model. Examples: * To unset the name attribute of a place. >>> attr_delete("models/my_pn", "p1", "name") .. function:: read_outgoing(model_name, ID, typename) Returns a list of all outgoing associations of *ID*, typed by *typename*, in model *model_name*. Typename can be set to the empty string to indicate that all types must match. Note that this returns the association itself, **NOT** the destination. Examples: * To get all arcs starting in place p1. >>> read_outgoing("models/my_pn", "p1", "P2T") ["p1_to_t1"] * To get all allowed connections starting in a Place. >>> read_outgoing("formalisms/PetriNets", "Place", "Association") ["P2T", "InhibitorArc"] .. function:: read_incoming(model_name, ID, typename) Returns a list of all incoming associations of *ID*, typed by *typename*, in model *model_name*. Typename can be set to the empty string to indicate that all types must match. Note that this returns the association itself, **NOT** the source. Examples: * To get all arcs going to place p1. >>> read_incoming("models/my_pn", "p1", "T2P") ["t1_to_p1"] * To get all allowed connections going to a Place. >>> read_incoming("formalisms/PetriNets", "Place", "Association") ["T2P"] .. function:: read_association_source(model_name, ID) Returns the identifier of the source of association *ID* in model *model_name*. Examples: * To read out the source of the P2T link. >>> read_association_source("formalisms/PetriNets", "P2T") "Place" * To read out the source of an arc. >>> read_association_source("models/my_pn", "p1_to_t1") "p1" .. function:: read_association_destination(model_name, ID) Returns the identifier of the target of association *ID* in model *model_name*. Examples: * To read out the target of the P2T link. >>> read_association_destination("formalisms/PetriNets", "P2T") "Transition" * To read out the target of an arc. >>> read_association_destination("models/my_pn", "p1_to_t1") "t1" .. function:: service_register(name, function) Registers the current client-side thread as a service in the Modelverse. The specified *function* will be executed when users make a request to the service specified by *name*. This function will be executed on a different thread for each incoming request for a new instance of the service. If required, synchronization must be built in manually. After making this call, the thread should stay alive, but can do whatever other operation is required. Note that, upon termination, this client **MUST** call the *service_stop()* function, as otherwise the Modelverse is not notified of this service deregistering. The service is initially invoked with a Modelverse port to use for communication. This port must be used in all requests made to the Modelverse in the context of this service. The only two supported operations now are *service_set* (send data to the Modelverse) and *service_get* (fetch data from the Modelverse). Examples: * To register a Fibonacci service. >>> def fibonacci_service(port): ... def fibonacci(value): ... if value <= 2: ... return 1 ... else: ... return fibonacci(value-1) + fibonacci(value-2) ... service_set(port, fibonacci, service_get(port)) >>> service_register("fibonacci", fibonacci_service) .. function:: service_stop() Stops the current service from being available to the Modelverse. Existing connections will not be forcibly terminated, and these run on separate threads. Examples: * To stop the currently executing service. >>> service_stop() .. function:: service_get(port) When running a serivce, it is often necessary to request data from the Modelverse. This data can be sent whatever way in the Modelverse. Different Modelverse executions are identified by their differing *port*, which is received upon the client invoking the external service. Note that this call is blocking. That is, only when data is available, will this function return. Examples: * To get data from a currently executing Modelverse client. >>> service_get(port) 5 .. function:: service_set(port, value) When running a service, it is often necessary to send data back to the Modelverse. This can be either for conversing with the user (e.g., prompting), or for sending the result back. Any primitive value can be sent to the Modelverse as a value. Examples: * To send the result back to the Modelverse. >>> service_set(port, 5) .. function:: user_password(user, password) Changes the password of user *user* to *password*. Permissions on this operation are of course rather strict: users can only modify their own password. Of course, an administrator can modify the password of every user. Active logins do not need to be refreshed, as the new username will automatically be used everywhere. From the next login, however, the new username has to be used, as the old one has been renamed. Examples: * To change the password of user *user1* to *my_password*. >>> user_password("user1", "my_password") .. function:: transformation_read_signature(transformation) Returns the signature of the transformation *transformation*. The signature consists of two dictionaries: the first specifies the input signature, and the second one specifies the output signature. Both dictionaries have the same structure: the keys represent the names of the model while invoking the operation, and the values represent the expected type of the model. Examples: * To read out the signature of the "plant_refine" operation. >>> transformation_read_signature("models/plant_refine") ({"req": "formalisms/Requirements", "plant": "formalisms/Plant"}, {"plant": "formalisms/Plant"}) .. function:: element_list_nice(model_name) Returns a complete representation of a model in a compact dicationary-like representation. This is basically a list of dictionaries, each dictionary containing the values usually obtained with *read_attrs*. Additionally, some special keys are added: *id* (the identifier returned upon instantiation), *__source* (the source of the association, if it was an association), and *__target* (the target of the association, if it was an association). Examples: * To read out a list of a PetriNets instance model. >>> element_list_nice("models/my_pn") [{"id": "p1", "name": "a place", "tokens": 1}, {"id": "t1", "name": "a transition"}, {"id": "p1_to_t1", "name": "transition", "__source": "p1", "__target": "t1", "weight": 1}] .. function:: connections_between(model_name, source_element, target_element) Returns a list of all allowed connection types between *source_element* and *target_element* in a specified model. Both elements need to be part of the same model, as otherwise a merged model should be created beforehand. Examples: * To read out the allowed connections between elements "p1" and "t1". >>> connections_between("models/my_pn", "p1", "t1") ["P2T"] * To read out the allowed connections from the Place class to itself. >>> connections_between("formalisms/PetriNets", "Place", "Place") ["Association", "Inheritance"] .. function:: define_attribute(model_name, node, attr_name, attr_type) Defines a new attribute on the element *node* in the metamodel identified by *model_name*. The attribute is named *attr_name* and must conform to *attr_type*. Note that, while there are other ways of creating attributes than this function, this function is by far the most user friendly. The type being assigned needs to be specified in the metamodel as well, by instantiating the *SimpleAttribute* node. As models are updated immediately, without any recompilation step inbetween, all instances can immediately make use of this new attribute. Examples: * To define a new attribute "tokens" on a PetriNet Place, which is of a Natural type. >>> define_attribute("formalisms/PetriNets", "Place", "tokens", Natural) .. function:: all_instances(model_name, type_name) Returns a list of all instances of type *type_name* in the model *model_name*. This includes indirect instances, which are only typed by type_name through inheritance. Examples: * To get all places in a PetriNet model. >>> all_instances("models/my_pn", "Place") ["p1", "__12345"] * To get all nodes that can be instantiated directly for PetriNets. >>> all_instances("formalisms/PetriNets", "Class") ["Place", "Transition", "P2T", "T2P"] .. function:: service_poll(port) Polls whether there is any input for the service we are hosting for communication handle *port*. This returns either True or False, to get the actual data, a *service_get(port)* is required. This function does not block when there is not data to be read. Examples: * To check whether there is any data for our port, when there is none. >>> service_poll(port) False * To check whether there is any data for our port, when there is some. >>> service_poll(port) True .. function:: user_name(user, username) Changes the username of user *user* to *username*. From this point on, the user has been renamed, and all old permissions are inherited. Similar to password management, users can only alter their own username. The administrator can also alter the username of all users. Active logins do not need to be refreshed, as the new username will automatically be used everywhere. From the next login, however, the new username has to be used, as the old one has been renamed. Examples: * To change the username of user1 to user2. >>> user_name("user1", "user2") .. function:: alter_context(model_name, metamodel_name) Registers the model *model_name* to be interpreted in the context of *metamodel_name* as its metamodel. All future requests to the model will be made, using the assumption that the model is typed by the specified metamodel. When this is not the case, the request will throw an exception. Most of the time, this call must not be manually made, unless switching between metamodels, or when it is the first time using the model. The context is automatically deduced from the previous commands: adding the model with a specific metamodel, automatically puts the model in that context. When the model is opened for the first time, and it was not created by the client itself (e.g., by someone else, or through a model transformation), the metamodel needs to be specified. Examples: * To change the default metamodel of PetriNets to Bottom. >>> alter_context("formalisms/PetriNets", "formalisms/Bottom") .. function:: allowed_metamodels(model_name) Fetch a set of all metamodels that are registered for *model_name*. This reads out the data in the Modelverse, and will not try to resolve new relations. It is possible for this list to be empty, in case the relation to *Bottom* was removed previously. Otherwise, every model will conform to at least *Bottom*. Other metamodels are optional. Examples: * To fetch the metamodels to which a PetriNet model conforms, where it is explicitly set that it also conforms to PetriNets which use inhibitor arcs. >>> allowed_metamodels("models/my_pn") ["formalisms/PetriNets", "formalisms/Bottom", "formalisms/PetriNets_Inhibitor"] * When all typing relations were explicitly removed. >>> allowed_metamodels("models/my_pn") [] * Usual situation when there is no metamodel specified. >>> allowed_metamodels("models/my_pn") ["Bottom"] .. function:: remove_metamodel(model_name, metamodel_name) Explicitly removes the conformance relation between *model_name* and *metamodel_name*. It will prevent future uses of this model in the context of this metamodel, though does not prevent future operations from recreating this relation. Examples: * To remove the PetriNets_Inhibitor metamodel for my_pn. >>> remove_metamodel("models/my_pn", "formalisms/PetriNets_Inhibitor") .. function:: add_metamodel(model_name, metamodel_name, partial_type_mapping=None) Explicitly add the conformance relation between *model_name* and *metamodel_name*. It creates the relation, and will try to find a type mapping between them automatically. A *partial_type_mapping* can be passed, which will be used as a starting point when searching for a conformance relation. Multiple possible relations might be found, in which case an arbitrary type mapping is taken. TODO: allow for the partial type mapping Examples: * To try and make my_pn conform to PetriNets_Inhibitor again; this results in multiple options! >>> add_metamodel("models/my_pn", "formalisms/PetriNets_Inhibitor") Exceptions ^^^^^^^^^^ Below is a list of all exceptions that the wrappers can raise, together with a summary of the kind of error that occured. .. exception:: ModelverseException Generic Modelverse Exception, of which all others inherit. This should be the only type of exception that the wrapper can raise. General Python exceptions could theoretically still occur, though this likely is a bug in the wrapper. This exception itself will never occur: it is abstract. .. exception:: UnknownError Unknown exception has occured. This is likely something wrong with the connection, such as the Modelverse that suddenly disconnected, or the request timed out. Note that this exception cannot be raised during *init*, as then the connection is nicely wrapped in a *ConnectionError*. Examples: * When the Modelverse is suddenly killed during execution, while there was an outstanding request. >>> element_list("formalisms/PetriNets") # <-- Modelverse killed during execution UnknownError() .. exception:: UnknownIdentifier The specified element identifier could not be resolved in the specified model in the Modelverse. The exception contains the identifier causing the problem, as there might be multiple identifiers used in a single request. Examples: * When reading out a non-existing element in a PetriNets model. >>> read("models/my_pn", "p0") UnknownIdentifier("p0") * When reading out the allowed connections between two elements, of which neither exists. >>> connections_between("models/my_pn", "p0", "t0") UnkownIdentifier("p0") * When reading out the allowed connections between two elements, of which the target doesn't exists. >>> connections_between("models/my_pn", "p1", "t0") UnkownIdentifier("t0") * When instantiating a non-existing element in the meta-model. >>> instantiate("models/my_pn", "CapacityConstrainedPlace") UnkownIdentifier("CapacityConstrainedPlace") .. exception:: UnsupportedValue The specified value is not a primitive that can be serialized. Supported types are: string, boolean, integer, and float. Examples: * When assigning a list as attribute. >>> attr_assign("models/my_pn", "p1", "tokens", [1, 2, 3]) UnsupporteValue("[1, 2, 3] : list") * When assigning a None value to an attribute. >>> attr_assign("models/my_pn", "p1", "name", None) UnsupportedValue("None : NoneType") .. exception:: CompilationError Error in the HUTN compiler during compilation. This is mostly caused by a malformed expression in the specified code. The compilation error is contained in the content of the exception. Examples: * When assigning a code block which cannot be parsed as action language. >>> attr_assign_code("models/my_pn", "p1", "tokens", "1") CompilationError("Parsing error at line 1: ...") .. exception:: NoSuchAttribute The specified attribute does not exist for this element. While the attribute might exist as a type for the lower meta-level, this only looks at the current level. Examples: * When assigning a non-existing attribute. >>> attr_assign("models/my_pn", "p1", "capacity", 2) NoSuchAttribute("capacity") .. exception:: UnknownModel The specified model can not be resolved in the Modelverse. While the model might technically exist, this exception merely indicates that it cannot be referred to with the specified identifier. Examples: * When trying to execute a non-existing transformation. >>> transformation_execute_MT("models/pn_optimize", {"pn": "models/my_pn"}, {"pn": "models/my_optimized_pn"}) UnknownModel("pn_optimize") .. exception:: ConnectionError Error during initialization of the connection to the Modelverse. The actual error is enclosed in the exception content. Despite possibly multiple errors, the *init* call will try for at least the amount of time specified in the timeout. This is done as the Modelverse might still be booting up and is not yet listening to incoming connections. Examples: * When trying to connect to a server which doesn't exist. >>> init("http://www.modelverse.be") ConnectionError("No such host") .. exception:: ModelExists The identifier to give to the newly created model already exists. Note that some operations, such as model transformation, will default to overwriting a model if it already exists, but still is used as a target. Of course, permissions of the overwritten model need to allow for this. Examples: * When the model "my_pn" already exists. >>> model_add("models/my_pn", "PetriNets") ModelExists("models/my_pn") .. exception:: PermissionDenied Permission denied to either write or read the specified resource. This resource can be anything: a model, an element, a user, a group, ... Examples: * When changing the password of another user, while we are not an administrator. >>> user_password("user2", "NewPassword") PermissionDenied("user2") * When listing the elements of a model which we aren't allowed to read. >>> element_list("models/secret_model") PermissionDenied("models/secret_model") * When altering a model which we are only allowed to read. >>> instantiate("formalisms/SimpleClassDiagrams", "NewClass") PermissionDenied("formalisms/SimpleClassDiagrams") .. exception:: InvalidMode An operation was executed in the wrong context. For example, all operations are only valid after a successful *init* and *login* call. This error can also result from a previous exception, which wasn't handled correctly. Examples: * When a *login* fails (thus raising a *PermissionDenied* exception), but the next operation assumes that the user is logged in. >>> login("admmin", "wrong_password") PermissionDenied("admin") >>> element_list("formalisms/SimpleClassDiagrams") InvalidMode() .. exception:: InterfaceMismatch The Modelverse responded with an unexpected response. As a response came, the Modelverse is likely still running, though we have no idea how to interpret the result. Likely, the wrapper is not up to date with the latest Modelverse operations. It is difficult to give an example, as this always indicates a bug in the wrapper itself. .. exception:: UnknownMetamodellingHierarchy The requested model and metamodel have no existing relation between them: it is unknown how to make the model conform to the metamodel. This might be because there is no possible relation, or just because there is no relation defined yet. Examples: * When erroneously trying to interpret a petrinet model as a Class Diagram. >>> alter_context("models/my_pn", "formalisms/SimpleClassDiagrams") >>> element_list() UnknownMetamodellingHierarchy("models/my_pn") Custom ------ Other wrappers can be made as desired, in whatever language required. This is due to the fact that the Modelverse communicates only through XML/HTTPRequests. As such, all languages that support this, can simply mimic the interface used by any of the implemented wrappers.