Skip to content

Commit 554501a

Browse files
authored
Merge pull request #764 from ManonMarchand/docs-samp
Docs samp
2 parents bd1ceb7 + 3554ca3 commit 554501a

10 files changed

Lines changed: 669 additions & 49 deletions

File tree

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Using ``pyvo``
135135
discover/index
136136
io/index
137137
auth/index
138-
samp
138+
samp/index
139139
mivot/index
140140
utils/index
141141
utils/prototypes

docs/nitpick-exceptions

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ py:obj pyvo.registry.regtap.Interface
1111
# There is no public API docs for this, yet it's useful to leave the reference in
1212
py:obj pyvo.io.vosi.exceptions
1313
py:class pyvo.dal.exceptions.PyvoUserWarning
14+
15+
# other classes and functions that cannot be linked to
16+
py:class xmlrpc.client.Error

docs/samp.rst

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
.. doctest-skip-all
2+
3+
Embedding a SAMP Hub in a GUI
4+
*****************************
5+
6+
Overview
7+
========
8+
9+
If you wish to embed a SAMP hub in your Python Graphical User Interface (GUI)
10+
tool, you will need to start the hub programmatically using::
11+
12+
from pyvo.samp import SAMPHubServer
13+
hub = SAMPHubServer()
14+
hub.start()
15+
16+
This launches the hub in a thread and is non-blocking. If you are not
17+
interested in connections from web SAMP clients, then you can use::
18+
19+
from pyvo.samp import SAMPHubServer
20+
hub = SAMPHubServer(web_profile=False)
21+
hub.start()
22+
23+
This should be all you need to do. However, if you want to keep the Web
24+
Profile active, there is an additional consideration: when a web
25+
SAMP client connects, you will need to ask the user whether they accept the
26+
connection (for security reasons). By default, the confirmation message is a
27+
text-based message in the terminal, but if you have a GUI tool, you will
28+
likely want to open a GUI dialog instead.
29+
30+
To do this, you will need to define a class that handles the dialog, and then
31+
pass an **instance** of the class to `pyvo.samp.SAMPHubServer` (not the class itself).
32+
This class should inherit from `pyvo.samp.WebProfileDialog` and add the
33+
following:
34+
35+
1) A GUI timer callback that periodically calls
36+
``WebProfileDialog.handle_queue`` (available as
37+
``self.handle_queue``).
38+
39+
2) A ``show_dialog`` method to display a consent dialog.
40+
It should take the following arguments:
41+
42+
- ``samp_name``: The name of the application making the request.
43+
44+
- ``details``: A dictionary of details about the client
45+
making the request. The only key in this dictionary required by
46+
the SAMP standard is ``samp.name`` which gives the name of the
47+
client making the request.
48+
49+
- ``client``: A hostname, port pair containing the client
50+
address.
51+
52+
- ``origin``: A string containing the origin of the
53+
request.
54+
55+
3) Based on the user response, the ``show_dialog`` should call
56+
``WebProfileDialog.consent`` or ``WebProfileDialog.reject``.
57+
This may, in some cases, be the result of another GUI callback.
58+
59+
Example of embedding a SAMP hub in a Tk application
60+
---------------------------------------------------
61+
62+
..
63+
EXAMPLE START
64+
Embedding a SAMP Hub in a Tk Application
65+
66+
The following code is a full example of a Tk application that watches for web
67+
SAMP connections and opens the appropriate dialog::
68+
69+
import tkinter as tk
70+
import tkinter.messagebox as tkMessageBox
71+
72+
from pyvo.samp import SAMPHubServer
73+
from pyvo.samp.hub import WebProfileDialog
74+
75+
MESSAGE = """
76+
A Web application which declares to be
77+
78+
Name: {name}
79+
Origin: {origin}
80+
81+
is requesting to be registered with the SAMP Hub. Pay attention
82+
that if you permit its registration, such application will acquire
83+
all current user privileges, like file read/write.
84+
85+
Do you give your consent?
86+
"""
87+
88+
class TkWebProfileDialog(WebProfileDialog):
89+
def __init__(self, root):
90+
self.root = root
91+
self.wait_for_dialog()
92+
93+
def wait_for_dialog(self):
94+
self.handle_queue()
95+
self.root.after(100, self.wait_for_dialog)
96+
97+
def show_dialog(self, samp_name, details, client, origin):
98+
text = MESSAGE.format(name=samp_name, origin=origin)
99+
100+
response = tkMessageBox.askyesno(
101+
'SAMP Hub', text,
102+
default=tkMessageBox.NO)
103+
104+
if response:
105+
self.consent()
106+
else:
107+
self.reject()
108+
109+
# Start up Tk application
110+
root = tk.Tk()
111+
tk.Label(root, text="Example SAMP Tk application",
112+
font=("Helvetica", 36), justify=tk.CENTER).pack(pady=200)
113+
root.geometry("500x500")
114+
root.update()
115+
116+
# Start up SAMP hub
117+
h = SAMPHubServer(web_profile_dialog=TkWebProfileDialog(root))
118+
h.start()
119+
120+
try:
121+
# Main GUI loop
122+
root.mainloop()
123+
except KeyboardInterrupt:
124+
pass
125+
126+
h.stop()
127+
128+
If you run the above script, a window will open that says "Example SAMP Tk
129+
application." If you then go to the following page, for example:
130+
131+
http://astrojs.github.io/sampjs/examples/pinger.html
132+
133+
and click on the Ping button, you will see the dialog open in the Tk
134+
application. Once you click on "CONFIRM," future "Ping" calls will no longer
135+
bring up the dialog.
136+
137+
..
138+
EXAMPLE END

docs/samp/example_clients.rst

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
.. doctest-skip-all
2+
3+
.. _vo-samp-example_clients:
4+
5+
6+
Communication between Integrated Clients Objects
7+
************************************************
8+
9+
As shown in :doc:`example_table_image`, the `pyvo.samp.SAMPIntegratedClient` class can be
10+
used to communicate with other SAMP-enabled tools such as `TOPCAT <https://www.star.bristol.ac.uk/mbt/topcat>`_,
11+
`SAO DS9 <http://ds9.si.edu/>`_, or `Aladin Desktop <https://aladin.unistra.fr>`_.
12+
13+
In this section, we look at how we can set up two `pyvo.samp.SAMPIntegratedClient`
14+
instances and communicate between them.
15+
16+
First, start up a SAMP hub as described in :doc:`example_hub`.
17+
18+
Next, we create two clients and connect them to the hub::
19+
20+
>>> from pyvo import samp
21+
>>> client1 = samp.SAMPIntegratedClient(name="Client 1", description="Test Client 1",
22+
... metadata = {"client1.version":"0.01"})
23+
>>> client2 = samp.SAMPIntegratedClient(name="Client 2", description="Test Client 2",
24+
... metadata = {"client2.version":"0.25"})
25+
>>> client1.connect()
26+
>>> client2.connect()
27+
28+
We now define functions to call when receiving a notification, call or
29+
response::
30+
31+
>>> def test_receive_notification(private_key, sender_id, mtype, params, extra):
32+
... print("Notification:", private_key, sender_id, mtype, params, extra)
33+
34+
>>> def test_receive_call(private_key, sender_id, msg_id, mtype, params, extra):
35+
... print("Call:", private_key, sender_id, msg_id, mtype, params, extra)
36+
... client1.ereply(msg_id, samp.SAMP_STATUS_OK, result = {"txt": "printed"})
37+
38+
>>> def test_receive_response(private_key, sender_id, msg_id, response):
39+
... print("Response:", private_key, sender_id, msg_id, response)
40+
41+
We subscribe client 1 to ``"samp.app.*"`` and bind it to the
42+
related functions::
43+
44+
>>> client1.bind_receive_notification("samp.app.*", test_receive_notification)
45+
>>> client1.bind_receive_call("samp.app.*", test_receive_call)
46+
47+
We now bind message tags received by client 2 to suitable functions::
48+
49+
>>> client2.bind_receive_response("my-dummy-print", test_receive_response)
50+
>>> client2.bind_receive_response("my-dummy-print-specific", test_receive_response)
51+
52+
We are now ready to test out the clients and callback functions. Client 2
53+
notifies all clients using the "samp.app.echo" message type via the hub::
54+
55+
>>> client2.enotify_all("samp.app.echo", txt="Hello world!")
56+
['cli#2']
57+
Notification: 0d7f4500225981c104a197c7666a8e4e cli#2 samp.app.echo {'txt':
58+
'Hello world!'} {'host': 'antigone.lambrate.inaf.it', 'user': 'unknown'}
59+
60+
We can also find a dictionary that specifies which clients would currently
61+
receive ``samp.app.echo`` messages::
62+
63+
>>> print(client2.get_subscribed_clients("samp.app.echo"))
64+
{'cli#2': {}}
65+
66+
Client 2 calls all clients with the ``"samp.app.echo"`` message type using
67+
``"my-dummy-print"`` as a message-tag::
68+
69+
>>> print(client2.call_all("my-dummy-print",
70+
... {"samp.mtype": "samp.app.echo",
71+
... "samp.params": {"txt": "Hello world!"}}))
72+
{'cli#1': 'msg#1;;cli#hub;;cli#2;;my-dummy-print'}
73+
Call: 8c8eb53178cb95e168ab17ec4eac2353 cli#2
74+
msg#1;;cli#hub;;cli#2;;my-dummy-print samp.app.echo {'txt': 'Hello world!'}
75+
{'host': 'antigone.lambrate.inaf.it', 'user': 'unknown'}
76+
Response: d0a28636321948ccff45edaf40888c54 cli#1 my-dummy-print
77+
{'samp.status': 'samp.ok', 'samp.result': {'txt': 'printed'}}
78+
79+
Client 2 then calls client 1 using the ``"samp.app.echo"`` message type,
80+
tagging the message as ``"my-dummy-print-specific"``::
81+
82+
>>> try:
83+
... print(client2.call(client1.get_public_id(),
84+
... "my-dummy-print-specific",
85+
... {"samp.mtype": "samp.app.echo",
86+
... "samp.params": {"txt": "Hello client 1!"}}))
87+
... except samp.SAMPProxyError as e:
88+
... print("Error ({0}): {1}".format(e.faultCode, e.faultString))
89+
msg#2;;cli#hub;;cli#2;;my-dummy-print-specific
90+
Call: 8c8eb53178cb95e168ab17ec4eac2353 cli#2
91+
msg#2;;cli#hub;;cli#2;;my-dummy-print-specific samp.app.echo {'txt': 'Hello
92+
Cli 1!'} {'host': 'antigone.lambrate.inaf.it', 'user': 'unknown'}
93+
Response: d0a28636321948ccff45edaf40888c54 cli#1 my-dummy-print-specific
94+
{'samp.status': 'samp.ok', 'samp.result': {'txt': 'printed'}}
95+
96+
We can now define a function called to test synchronous calls::
97+
98+
>>> def test_receive_sync_call(private_key, sender_id, msg_id, mtype, params, extra):
99+
... import time
100+
... print("SYNC Call:", sender_id, msg_id, mtype, params, extra)
101+
... time.sleep(2)
102+
... client1.reply(msg_id, {"samp.status": samp.SAMP_STATUS_OK,
103+
... "samp.result": {"txt": "printed sync"}})
104+
105+
We now bind the ``samp.test`` message type to ``test_receive_sync_call``::
106+
107+
>>> client1.bind_receive_call("samp.test", test_receive_sync_call)
108+
>>> try:
109+
... # Sync call
110+
... print(client2.call_and_wait(client1.get_public_id(),
111+
... {"samp.mtype": "samp.test",
112+
... "samp.params": {"txt": "Hello SYNCRO client 1!"}},
113+
... "10"))
114+
... except samp.SAMPProxyError as e:
115+
... # If timeout expires than a SAMPProxyError is returned
116+
... print("Error ({0}): {1}".format(e.faultCode, e.faultString))
117+
SYNC Call: cli#2 msg#3;;cli#hub;;cli#2;;sampy::sync::call samp.test {'txt':
118+
'Hello SYNCRO Cli 1!'} {'host': 'antigone.lambrate.inaf.it', 'user':
119+
'unknown'}
120+
{'samp.status': 'samp.ok', 'samp.result': {'txt': 'printed sync'}}
121+
122+
Finally, we disconnect the clients from the hub at the end::
123+
124+
>>> client1.disconnect()
125+
>>> client2.disconnect()

docs/samp/example_hub.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.. doctest-skip-all
2+
3+
.. _vo-samp-example_hub:
4+
5+
Starting and Stopping a SAMP Hub Server
6+
***************************************
7+
8+
There are several ways you can start up a SAMP hub:
9+
10+
Using an Existing Hub
11+
=====================
12+
13+
You can start up another application that includes a hub, such as
14+
`TOPCAT <https://www.star.bristol.ac.uk/mbt/topcat>`_, `SAO DS9 <http://ds9.si.edu/>`_, or
15+
`Aladin Desktop <https://aladin.unistra.fr>`_.
16+
17+
18+
Starting a Hub Programmatically
19+
===============================
20+
21+
You can start up a hub by creating a `pyvo.samp.SAMPHubServer` instance and starting it,
22+
either from the interactive Python prompt, or from a Python script::
23+
24+
>>> from pyvo.samp import SAMPHubServer
25+
>>> hub = SAMPHubServer()
26+
>>> hub.start()
27+
28+
You can then stop the hub by calling::
29+
30+
>>> hub.stop()
31+
32+
However, this method is generally not recommended for average users because it
33+
does not work correctly when web SAMP clients try to connect. Instead, this
34+
should be reserved for developers who want to embed a SAMP hub in a GUI, for
35+
example. For more information, see :doc:`advanced_embed_samp_hub`.

0 commit comments

Comments
 (0)