Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Move astropy.samp to PyVO #239

Closed
wants to merge 9 commits into from
140 changes: 140 additions & 0 deletions docs/astropy_samp/advanced_embed_samp_hub.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
.. include:: references.txt

.. doctest-skip-all

Embedding a SAMP Hub in a GUI
*****************************

Overview
========

If you wish to embed a SAMP hub in your Python Graphical User Interface (GUI)
tool, you will need to start the hub programmatically using::

from pyvo.astropy_samp import SAMPHubServer
hub = SAMPHubServer()
hub.start()

This launches the hub in a thread and is non-blocking. If you are not
interested in connections from web SAMP clients, then you can use::

from pyvo.astropy_samp import SAMPHubServer
hub = SAMPHubServer(web_profile=False)
hub.start()

This should be all you need to do. However, if you want to keep the Web
Profile active, there is an additional consideration: when a web
SAMP client connects, you will need to ask the user whether they accept the
connection (for security reasons). By default, the confirmation message is a
text-based message in the terminal, but if you have a GUI tool, you will
likely want to open a GUI dialog instead.

To do this, you will need to define a class that handles the dialog, and then
pass an **instance** of the class to |SAMPHubServer| (not the class itself).
This class should inherit from `pyvo.astropy_samp.WebProfileDialog` and add the
following:

1) A GUI timer callback that periodically calls
``WebProfileDialog.handle_queue`` (available as
``self.handle_queue``).

2) A ``show_dialog`` method to display a consent dialog.
It should take the following arguments:

- ``samp_name``: The name of the application making the request.

- ``details``: A dictionary of details about the client
making the request. The only key in this dictionary required by
the SAMP standard is ``samp.name`` which gives the name of the
client making the request.

- ``client``: A hostname, port pair containing the client
address.

- ``origin``: A string containing the origin of the
request.

3) Based on the user response, the ``show_dialog`` should call
``WebProfileDialog.consent`` or ``WebProfileDialog.reject``.
This may, in some cases, be the result of another GUI callback.

Example of embedding a SAMP hub in a Tk application
---------------------------------------------------

..
EXAMPLE START
Embedding a SAMP Hub in a Tk Application

The following code is a full example of a Tk application that watches for web
SAMP connections and opens the appropriate dialog::

import tkinter as tk
import tkinter.messagebox as tkMessageBox

from pyvo.astropy_samp import SAMPHubServer
from pyvo.astropy_samp.hub import WebProfileDialog

MESSAGE = """
A Web application which declares to be

Name: {name}
Origin: {origin}

is requesting to be registered with the SAMP Hub. Pay attention
that if you permit its registration, such application will acquire
all current user privileges, like file read/write.

Do you give your consent?
"""

class TkWebProfileDialog(WebProfileDialog):
def __init__(self, root):
self.root = root
self.wait_for_dialog()

def wait_for_dialog(self):
self.handle_queue()
self.root.after(100, self.wait_for_dialog)

def show_dialog(self, samp_name, details, client, origin):
text = MESSAGE.format(name=samp_name, origin=origin)

response = tkMessageBox.askyesno(
'SAMP Hub', text,
default=tkMessageBox.NO)

if response:
self.consent()
else:
self.reject()

# Start up Tk application
root = tk.Tk()
tk.Label(root, text="Example SAMP Tk application",
font=("Helvetica", 36), justify=tk.CENTER).pack(pady=200)
root.geometry("500x500")
root.update()

# Start up SAMP hub
h = SAMPHubServer(web_profile_dialog=TkWebProfileDialog(root))
h.start()

try:
# Main GUI loop
root.mainloop()
except KeyboardInterrupt:
pass

h.stop()

If you run the above script, a window will open that says "Example SAMP Tk
application." If you then go to the following page, for example:

http://astrojs.github.io/sampjs/examples/pinger.html

and click on the Ping button, you will see the dialog open in the Tk
application. Once you click on "CONFIRM," future "Ping" calls will no longer
bring up the dialog.

..
EXAMPLE END
129 changes: 129 additions & 0 deletions docs/astropy_samp/example_clients.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
.. include:: references.txt

.. doctest-skip-all

.. _vo-samp-example_clients:


Communication between Integrated Clients Objects
************************************************

As shown in :doc:`example_table_image`, the |SAMPIntegratedClient| class can be
used to communicate with other SAMP-enabled tools such as `TOPCAT
<http://www.star.bris.ac.uk/~mbt/topcat/>`_, `SAO DS9
<http://ds9.si.edu/>`_, or `Aladin Desktop
<http://aladin.u-strasbg.fr>`_.

In this section, we look at how we can set up two |SAMPIntegratedClient|
instances and communicate between them.

First, start up a SAMP hub as described in :doc:`example_hub`.

Next, we create two clients and connect them to the hub::

>>> from pyvo import astropy_samp as samp
>>> client1 = samp.SAMPIntegratedClient(name="Client 1", description="Test Client 1",
... metadata = {"client1.version":"0.01"})
>>> client2 = samp.SAMPIntegratedClient(name="Client 2", description="Test Client 2",
... metadata = {"client2.version":"0.25"})
>>> client1.connect()
>>> client2.connect()

We now define functions to call when receiving a notification, call or
response::

>>> def test_receive_notification(private_key, sender_id, mtype, params, extra):
... print("Notification:", private_key, sender_id, mtype, params, extra)

>>> def test_receive_call(private_key, sender_id, msg_id, mtype, params, extra):
... print("Call:", private_key, sender_id, msg_id, mtype, params, extra)
... client1.ereply(msg_id, samp.SAMP_STATUS_OK, result = {"txt": "printed"})

>>> def test_receive_response(private_key, sender_id, msg_id, response):
... print("Response:", private_key, sender_id, msg_id, response)

We subscribe client 1 to ``"samp.app.*"`` and bind it to the
related functions::

>>> client1.bind_receive_notification("samp.app.*", test_receive_notification)
>>> client1.bind_receive_call("samp.app.*", test_receive_call)

We now bind message tags received by client 2 to suitable functions::

>>> client2.bind_receive_response("my-dummy-print", test_receive_response)
>>> client2.bind_receive_response("my-dummy-print-specific", test_receive_response)

We are now ready to test out the clients and callback functions. Client 2
notifies all clients using the "samp.app.echo" message type via the hub::

>>> client2.enotify_all("samp.app.echo", txt="Hello world!")
['cli#2']
Notification: 0d7f4500225981c104a197c7666a8e4e cli#2 samp.app.echo {'txt':
'Hello world!'} {'host': 'antigone.lambrate.inaf.it', 'user': 'unknown'}

We can also find a dictionary that specifies which clients would currently
receive ``samp.app.echo`` messages::

>>> print(client2.get_subscribed_clients("samp.app.echo"))
{'cli#2': {}}

Client 2 calls all clients with the ``"samp.app.echo"`` message type using
``"my-dummy-print"`` as a message-tag::

>>> print(client2.call_all("my-dummy-print",
... {"samp.mtype": "samp.app.echo",
... "samp.params": {"txt": "Hello world!"}}))
{'cli#1': 'msg#1;;cli#hub;;cli#2;;my-dummy-print'}
Call: 8c8eb53178cb95e168ab17ec4eac2353 cli#2
msg#1;;cli#hub;;cli#2;;my-dummy-print samp.app.echo {'txt': 'Hello world!'}
{'host': 'antigone.lambrate.inaf.it', 'user': 'unknown'}
Response: d0a28636321948ccff45edaf40888c54 cli#1 my-dummy-print
{'samp.status': 'samp.ok', 'samp.result': {'txt': 'printed'}}

Client 2 then calls client 1 using the ``"samp.app.echo"`` message type,
tagging the message as ``"my-dummy-print-specific"``::

>>> try:
... print(client2.call(client1.get_public_id(),
... "my-dummy-print-specific",
... {"samp.mtype": "samp.app.echo",
... "samp.params": {"txt": "Hello client 1!"}}))
... except samp.SAMPProxyError as e:
... print("Error ({0}): {1}".format(e.faultCode, e.faultString))
msg#2;;cli#hub;;cli#2;;my-dummy-print-specific
Call: 8c8eb53178cb95e168ab17ec4eac2353 cli#2
msg#2;;cli#hub;;cli#2;;my-dummy-print-specific samp.app.echo {'txt': 'Hello
Cli 1!'} {'host': 'antigone.lambrate.inaf.it', 'user': 'unknown'}
Response: d0a28636321948ccff45edaf40888c54 cli#1 my-dummy-print-specific
{'samp.status': 'samp.ok', 'samp.result': {'txt': 'printed'}}

We can now define a function called to test synchronous calls::

>>> def test_receive_sync_call(private_key, sender_id, msg_id, mtype, params, extra):
... import time
... print("SYNC Call:", sender_id, msg_id, mtype, params, extra)
... time.sleep(2)
... client1.reply(msg_id, {"samp.status": samp.SAMP_STATUS_OK,
... "samp.result": {"txt": "printed sync"}})

We now bind the ``samp.test`` message type to ``test_receive_sync_call``::

>>> client1.bind_receive_call("samp.test", test_receive_sync_call)
>>> try:
... # Sync call
... print(client2.call_and_wait(client1.get_public_id(),
... {"samp.mtype": "samp.test",
... "samp.params": {"txt": "Hello SYNCRO client 1!"}},
... "10"))
... except samp.SAMPProxyError as e:
... # If timeout expires than a SAMPProxyError is returned
... print("Error ({0}): {1}".format(e.faultCode, e.faultString))
SYNC Call: cli#2 msg#3;;cli#hub;;cli#2;;sampy::sync::call samp.test {'txt':
'Hello SYNCRO Cli 1!'} {'host': 'antigone.lambrate.inaf.it', 'user':
'unknown'}
{'samp.status': 'samp.ok', 'samp.result': {'txt': 'printed sync'}}

Finally, we disconnect the clients from the hub at the end::

>>> client1.disconnect()
>>> client2.disconnect()
51 changes: 51 additions & 0 deletions docs/astropy_samp/example_hub.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.. include:: references.txt

.. doctest-skip-all

.. _vo-samp-example_hub:

Starting and Stopping a SAMP Hub Server
***************************************

There are several ways you can start up a SAMP hub:

Using an Existing Hub
=====================

You can start up another application that includes a hub, such as
`TOPCAT <http://www.star.bris.ac.uk/~mbt/topcat/>`_,
`SAO DS9 <http://ds9.si.edu/>`_, or
`Aladin Desktop <http://aladin.u-strasbg.fr>`_.

Using the Command-Line Hub Utility
==================================

You can make use of the ``samp_hub`` command-line utility, which is included in
``pyvo``::

$ samp_hub

To get more help on available options for ``samp_hub``::

$ samp_hub -h

To stop the server, press control-C.

Starting a Hub Programmatically (Advanced)
==========================================

You can start up a hub by creating a |SAMPHubServer| instance and starting it,
either from the interactive Python prompt, or from a Python script::

>>> from pyvo.astropy_samp import SAMPHubServer
>>> hub = SAMPHubServer()
>>> hub.start()

You can then stop the hub by calling::

>>> hub.stop()

However, this method is generally not recommended for average users because it
does not work correctly when web SAMP clients try to connect. Instead, this
should be reserved for developers who want to embed a SAMP hub in a GUI, for
example. For more information, see :doc:`advanced_embed_samp_hub`.
Loading