Package: gama-client
Gama clientGama-client is a Python module designed to facilitate interactions with the headless mode of the modeling and simulation platform known asgama. It is compatible with gama version 1.9.3. This wrapper handles the connection to gama-server and sends properly formatted requests. It is designed to accommodate the asynchronous nature of gama-server, enabling users to manage multiple simulations simultaneously. However, users are responsible for handling received messages, such as command confirmations, simulation outputs, and errors. We provide a functional example that demonstrates how to structure your code for sequential execution.Starting from version 1.2.0 of the wrapper, a new class has been introduced to allow synchronous (blocking) calls to gama-server. It's important to note that using this feature may lead to issues in cases where errors originate from gama-server, as the code might indefinitely wait for a response that will never arrive.InstallationIn your python environment, install the gama-client package with the command:pip install gama-clientFor advanced users, you can find the package on thepypi websiteand do it yourself instead of using thepiptool.You can check that everything went well by opening a python console and try the following line:fromgama_client.base_clientimportGamaBaseClientIf you don't see any error message thengama-clienthas been installed correctly.Using itRequirementsTo usegama-clientyou first need to have an instance ofgama-serveropen and the python package installed.
Then you can interact with gama-server in python using theGamaBaseClientorGamaSyncClientclass.Available functionsThe wrapper supports all the commands described in the gama-serverdocumentation.Quick overviewGamaBaseClientAs previously mentioned, theGamaBaseClientclass serves as the typical method for engaging with gama-server, and it operates in an asynchronous manner. Consequently, all messages from gama-server are delivered to a function,
which you must customize to respond according to your program's current state and the content of the received messages.Before doing anything you will have to create an instance of that class with theurlandportof the running gama-server as well as the function that should be called when a message is received.
for example to connect to a local gama-server running on port 6868 and printing received message:importasynciofromgama_client.base_clientimportGamaBaseClientasyncdefmessage_handler(message):print("received message:",message)asyncdefmain():client=GamaBaseClient("localhost",6868,message_handler)awaitclient.connect(False)whileTrue:awaitasyncio.sleep(1)if__name__=="__main__":asyncio.run(main())When running main, your python program should connect to gama-server and when theConnectionSuccessfulmessage is sent back to python, the console should show this:received message:{'type':'ConnectionSuccessful','content':'480777042'}Note:make sure to define yourmessage_handleras anasyncfunction, as it's what's expected byGamaBaseClient.As explained in the gama-server documentationhereandthereyou should then use thecontentvalue (here '480777042') as asocket idin the rest of your interactions with gama-server.
The classGamaBaseClientcontains a variablesocket_idthat you can use to store the socket id of your client, or for more simplicity you can connect with:awaitclient.connect(True)orawaitclient.connect()And the client will take care of handling this first message and setting thesocket_idby itself.To explicitly disconnect from gama-server, simply use theclose_connectionfunction:awaitclient.close_connection()you can later reconnect with the sameclientobject the same way you did the first time, with theconnectfunction.GamaSyncClientAlternatively you can use the classGamaSyncClientto establish the connection. This is recommended for simple scripts as it makes the call of commands
easier, but you may encounter some limitation in case of exceptions raised by gama-server for example.
This class inherits fromGamaBaseClientand so you can also use it to run async commands if you wish to.The connection works like this:importasynciofromgama_client.sync_clientimportGamaSyncClientfromtypingimportDictasyncdefasync_command_answer_handler(message:Dict):print("Here is the answer to an async command: ",message)asyncdefgama_server_message_handler(message:Dict):print("I just received a message from Gama-server and it's not an answer to a command!")print("Here it is:",message)asyncdefmain():client=GamaSyncClient("localhost",6868,async_command_answer_handler,gama_server_message_handler)awaitclient.connect(False)whileTrue:awaitasyncio.sleep(1)if__name__=="__main__":asyncio.run(main())This time, you have to provide two message handlers, the first one will be used only for answers of asynchronous commands (the same as inGamaBaseClient), the second one will be used for all kind of messages that are not answers to commands (synchronous or not), like for example status bar updates, messages writen in the console, some exceptions etc..Theconnectfunction works exactly as theGamaBaseClientand all the remarks made previously hold forGamaSyncClientRunning commandsOnce connected you will want to run commands, the principle is pretty simple: all commands can be from run yourclientvariable through functions. For example if you want to run theload commandyou just have to call theloadfunction with the proper parameters.awaitclient.load("path/to/gaml/file","my_experiment_name")If you are using aGamaBaseClient, the program will proceed once the command is dispatched to gama-server. However, it won't pause to await the response, whether it's a success, failure, or the result of an expression you requested for evaluation. You will need to manage the response within themessage_handlerfunction you established when creating your client.With a client of typeGamaSyncClient, you have access to those functions too plus synchronous ones. With synchronous functions the program will stop at each command call and wait for gama-server to answer, then the answer will be passed as the return of the command function.
Those functions are preceded by the wordsync. For example the synchronous load functions is called this way:command_answer=awaitclient.sync_load("path/to/gaml/file","my_experiment_name")and the variablecommand_answerwill contain the result sent by gama-server containing the experiment id.Message handlingFiltering messagesThe messages sent back by gama-server all follow the json format and are converted into a python dictionary by the wrapper. Those messages all have a field calledtypethat can help you discriminate between them. The complete list of types and what they correspond to is given in thedocumentation. And on the python's side you can use the enumMessageTypesto test the type of a received message.
Here is an example of amessage_handlerfunction that prints a personalised message when a command has been executed successfully:asyncdefmessage_handler(message):if"type"inmessage.keys()andmessage['type']==MessageTypes.CommandExecutedSuccessfully.value:print("congratulation, a command was executed successfully !")else:print("other kind of message",message)Notes:If you use theMessageTypesenum, don't forget to use thevalueattribute to compare it to stringsAs explained before,message_handlermustbeasynceven if you don't useawaitinsideRetrieving a command's answerIf you run your client purely asynchronous and have multiple simulations running at the same time, you will encounter the problem of retrieving which message corresponds to which command/simulation.For the simulation outputs or errors, they simply include anexperiment_idfield that will tell you exactly to which experiment the message corresponds to.
Answers to commands include acommandfield, containing the entirety of the command it responds to.In every command function, there is an optional parameter calledadditional_data, you can use it to store metadata about your command, for example an id, and use it to find to which precise command does an answer responds to because those additional data will also be stored in thecommandfield of the answer.For example here we run 3 identical load commands, and we want to have a special treatment for the second one only, so we give it a special id in theadditional_datain order to find the corresponding answer in themessage-handlerfunction:importasynciofromtypingimportDictfromgama_client.base_clientimportGamaBaseClientload_command_secret_id=123other_id=1asyncdefmessage_handler(message:Dict):if"command"inmessage.keys()and"my_secret_id"inmessage['command']andmessage['command']["my_secret_id"]==load_command_secret_id:print("answer for the load command we wanted to retrieve received:",message)else:print("other kind of message",message)asyncdefmain():client=GamaBaseClient("localhost",6868,message_handler)awaitclient.connect()gaml_file="path/to/gaml/file"expriment="name of the experiment"# this is not the command we want to retrieveawaitclient.load(gaml_file,expriment,additional_data={"my_secret_id":other_id})# this is the command we want to retrieve the answer toawaitclient.load(gaml_file,expriment,additional_data={"my_secret_id":load_command_secret_id})# this is not the command we want to retrieveawaitclient.load(gaml_file,expriment,additional_data={"my_secret_id":other_id})whileTrue:awaitasyncio.sleep(1)if__name__=="__main__":asyncio.run(main())Example codeSome working examples are provided in theexamplesdirectory, you just have to change the values of the variablesserver_url,server_port,gaml_file_path,exp_nameandexp_parametersto the one corresponding to your own gama-server and experiment to try it.
The examplesequential_example.pyfocuses on the use ofGamaBaseClientwhilesync_client_example.pyfocuses onGamaSyncClient.To generate a new release (for contributors only)Upload the new files to pypiFor reference: this documentation is based on thistutorial. Please check it out for more details about tools to install and/or context.increment the version number inpyproject.tomlcommit your changes to githubmake sure you installed the required tools by runningpython -m pip install --upgrade buildpython -m pip install --upgrade twinein the project's folder, to build the library, runpython -m buildthen, to upload the generated files, runpython -m twine upload --repository pypi dist/*on github, go to releasesclick on create a new releasein tag, create a new tag with the new release numberadd a description of the changesin binaries, upload the two .whl files generated by thebuildcommandclick onpublish release