Welcome to FullPy’s documentation!
FullPy is a high-level Python module for developping client-server web application. Here are the main features:
Both client and server are written in Python, and can share pieces of code. FullPy uses Brython for client-side execution of Python in the web browser.
Semantic-aware data persistance using OWL ontologies instead of a database. FullPy uses Owlready2 for managing ontologies and automatically storing them in a SQLite3 database.
Remote function calls between client and server, with object serialization. FullPy can use both Ajax (single way client->server calls) or WebSockets (client->server and server->client calls)
FullPy also provides many high-level services, such as authentication, translation support, HTML widget system, etc.
FullPy can run over multiple backend: Flask, Werkzeug and Gunicorn (only Gunicorn is supported for WebSockets).
Table of content
Installing FullPy
FullPy can be installed with ‘pip’, the Python Package Installer.
Installation from terminal (Bash under Linux or DOS under Windows)
You can use the following Bash / DOS commands to install FullPy in a terminal:
pip install fullpy
If you don’t have the permissions for writing in system files, you can install FullPy in your user directory with this command:
pip install --user fullpy
Installation in Spyder, IDLE, or any other Python console
You can use the following Python commands to install FullPy from a Python console (including those found in Spyder3 or IDLE):
>>> import pip.__main__
>>> pip.__main__._main(["install", "--user", "fullpy"])
Manual installation
FullPy can also be installed manually in 3 steps:
# Uncompress the FullPy-0.1.tar.gz source release file (or any other version), for example in C:\ under Windows
# Rename the directory C:\FullPy-0.1 as C:\fullpy
# Add the C:\ directory in your PYTHONPATH; this can be done in Python as follows:
import sys sys.path.append("C:\") import fullpy
Starting a new project with FullPy
Directory architecture
To start a new project, follow these simple steps:
Create a directory for your project
Create a “static” subdirectory in that directory
Copy the following files in the “static” subdirectory:
“fullpy.css” (from fullpy/static)
“brython.js” and “brython_stdlib.js” (from Brython)
Create a “server.py” and “client.py” Python scripts in your project directory
That’s all! You should obtain the following hierarchy:
- project_directory/
- static/
brython.js
brython_stdlib.js
fullpy.css
client.py
server.py
The two next subsections give an example of an Hello World FullPy web application (the code can be found in the “demo/demo_1_hello_world_ajax” directory of FullPy).
Hello World server example
import sys, os, os.path
from fullpy.server import *
class MyWebApp(ServerSideWebapp):
def __init__(self):
ServerSideWebapp.__init__(self)
self.name = "demo"
self.url = "/index.html"
self.title = "FullPy demo"
self.static_folder = os.path.join(os.path.dirname(__file__), "static")
self.use_python_client(os.path.join(os.path.dirname(__file__), "client.py"))
self.use_ajax(debug = True)
@rpc # Mark the function as remotely callable by the client (RPC = remote procedure call)
def server_hello(self, session):
return "Hello world!"
from fullpy.server.gunicorn_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000")
Hello World client example
from fullpy.client import *
class MyWebApp(ClientSideWebapp):
def on_started(self):
def done(response):
html = HTML("""FullPy Demo loaded Ok! Server says: '%s'.""" % response)
html.show()
webapp.server_hello(done) # Call the server_hello() remote function on the server
MyWebApp()
Running the web application
To run your web application, simply execute the server.py Python script.
FullPy will automatically compile the client part of the web application into Javascript, if needed.
python3 ./server.py
Then, open the following address in your web browser: http://127.0.0.1:5000/demo/index.html
Server application
The server application should import fullpy.server, subclass fullpy.server.ServerSideWebapp, create an instance of the subclass and pass it to server_forever(), as in the following example:
import sys, os, os.path
from fullpy.server import *
class MyWebApp(ServerSideWebapp):
def __init__(self):
ServerSideWebapp.__init__(self)
self.name = "demo"
self.url = "/index.html"
self.title = "FullPy demo"
self.static_folder = os.path.join(os.path.dirname(__file__), "static")
self.js = []
self.css = []
self.favicon = "icon.png"
self.use_python_client(os.path.join(os.path.dirname(__file__), "client.py"))
self.use_ajax(debug = True)
from fullpy.server.gunicorn_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000")
In the subclass of ServerSideWebapp, you need to reimplement __init__(). Your __init__() should call the super implementation, then define some properties and finally call some use_XXX() methods.
The following properties are available:
name
(mandatory): the name of the web app.url
(mandatory): the URL of the web app page. The full URL with be the concatenation of the server address, the name property and the url property (in the example above, “http://127.0.0.1:5000/demo/index.html”).title
(mandatory): the title of the web app (showed in the web browser’s window titlebar).static_folder
(mandatory): the local directory where static files are stored.js
(optional): a list of additional Javascript files used by the client. Notice that FullPy automatically adds Brython Javascript files as needed. Javascript files are expected to be found in the static directory.css
(optional): a list of CSS files used by the client. Notice that FullPy automatically adds “fullpy.css”. CSS files are expected to be found in the static directory.favicon
(optional): the “favicon” image file, showed in the web browser. The favicon is expected to be found in the static directory.
use_XXX() methods are used to enable various features of FullPy. The following use_XXX() methods are available:
- use_python_client(client_file, force_brython_compilation=False, minify_python_code=False)
Use a Brython-compiled Python client. The client is automatically compiled to Javascript as needed.
client_file
: the path to the client Python script (e.g. client.py).force_brython_compilation
: if True, compile the client even if its sources have not been modified.minify_python_code
: if True, minify the Python source using the “python_minifier” module.
- use_ontology_quadstore(world=None)
Use an Owlready2 quadstore for persistent data and semantics.
world
: the Owlready2 World to use (if None, owlread2.default_world is used).
- use_session(session_class=None, group_class=None, auth=True, client_reloadable_session=True, session_max_duration=3888000.0, session_max_memory_duration=1296000.0)
Use sessions. A session is an object available on the server application, and created for each connected client. If a given client calls several remote functions on the server, each call will be associated with the same session. It thus allows storing client-specific information on the server-side.
FullPy support both anonymous sessions (automatically created by the client) and authentified sessions (with login and password). However, the use of authentified sessions requires an ontology quadstore, for storing users and their logins and passwords.
In addition, FullPy support both in-memory sessions (lost when the server is stopped and restarted) and persistent sessions (stored in the ontology quadstore). The use of persistent sessions requires an ontology quadstore.
session_class
: the Session class to use. If None, use the default, in-memory, Session class. You can provide your own Session class, to reimplement some methods, store additional per-session data, and/or create a persistent Session.group_class
: the Group class to use. If None, use the default, in-memory, Group class. You can provide your own Group class, to reimplement some methods, store additional per-group data, and/or create a persistent Group.auth
: if True, support authentified sessions (which requires an ontology quadstore).client_reloadable_session
: if True, allows the client to reload and reuse the previous session.session_max_duration
: after that duration (in seconds), sessions are closed and destroyed. Default value corresponds to 45 days.session_max_memory_duration
: after that duration (in seconds), sessions are removed from the memory (but still kept in the ontology quadstore, if persistent sessions are used). Default value corresponds to 15 days.
- use_ajax(debug=False)
Use Ajax, allowing client->server remote function calls. On the contrary, the server cannot call remote functions on the client with Ajax.
debug
: if True, debugging information is written in the console each time a remote function is called.
- use_websocket(debug=False)
Use WebSocket, allowing both client->server and server->client remote function calls. WebSockets require the use of sessions (anonymous or authentified).
debug
: if True, debugging information is written in the console each time a remote function is called (for both the client and the server).
The following combination of use_XXX() methods are allowed:
use_python_client(); use_ajax():
Simple Ajax-based web application, without sessions nor data persistance.
use_python_client(); use_ontology_quadstore(); use_ajax():
Ajax-based web application, with data persistance but without sessions. Can be used e.g. for a dynamic website based on an ontology.
use_python_client(); use_session(auth = False); use_ajax():
Ajax-based web application, with anonymous sessions but without data persistance. Anonymous sessions can be used e.g. for keeping user preferences (such as language) during navigation, but the preferences will be lost when the user closes the web browser.
use_python_client(); use_ontology_quadstore(); use_session(); use_ajax():
Ajax-based web application, with both sessions and data persistance. Sessions can be anonymous or authentified.
use_python_client(); use_session(auth = False); use_websocket():
WebSocket-based web application, with anonymous sessions but without data persistance.
use_python_client(); use_ontology_quadstore(); use_session(); use_websocket():
WebSocket-based web application, with both sessions and data persistance. Sessions can be anonymous or authentified.
Additionnally, when using WebSocket, you need to enable GEvent and to use the Gunicorn backend, as in the following example:
from gevent import monkey
monkey.patch_all()
import sys, os, os.path
from fullpy.server import *
class MyWebApp(ServerSideWebapp):
def __init__(self):
ServerSideWebapp.__init__(self)
self.name = "demo"
self.url = "/index.html"
self.title = "FullPy demo"
self.static_folder = os.path.join(os.path.dirname(__file__), "static")
self.use_python_client(os.path.join(os.path.dirname(__file__), "client.py"))
self.use_websocket(debug = True)
from fullpy.server.gunicorn_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000")
Finally, ClientSideWebapp has the following methods that can be reimplemented:
- get_initial_data(url_params)
Create the initial data sent to the client. By default, no initial data is sent, but you can override this method to send some initial data. Initial data will be incorporated in the HTML webpage. They will be encoded with repr() (NB FullPy do not use the serializer to encode initial data, so as you may send a dictionary, and then decode its content one piece at a time, when needed).
url_params
: a dictionary with the parameters found in the query part of the URL.
Client application
The client application should import fullpy.client, subclass fullpy.server.ClientSideWebapp, and create an instance of the subclass, as in the following example:
from fullpy.client import *
class MyWebApp(ClientSideWebapp):
def on_started(self):
html = HTML("""FullPy Demo loaded Ok!""")
html.show()
MyWebApp()
The web application object can be accessed anywhere with the webapp
global built-in variable.
ClientSideWebapp instances have the following attributes:
url_params
: a dictionary with the parameters found in the query part of the URL.
initial_data
: the initial data sent by the server (if any).
ClientSideWebapp has the following methods that can be reimplemented:
- on_started()
Called once, when the web app starts.
- on_session_opened(user_login, user_class, client_data)
Called when a session is opened, and after on_started(). Notice that it is also called for anonymous sessions (in that case,
user_login
is empty).user_login
: the login of the user.user_class
: the name of the class of the user (as a string; usefull if you define several subclasses of User).client_data
: the additional client data sent by the server (if any).
- on_connexion_lost()
Called when the connexion to the server is lost.
- on_session_lost()
Called when the session is lost (e.g. it has expired).
Backends
FullPy supports several backend servers.
Gunicorn backend
The Gunicorn backend supports both Ajax and WebSockets web apps. Ajax web apps can be run with or without GEvent. WebSockets web apps automatically use Gevent.
from fullpy.server.gunicorn_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000", url_prefix = "", flask_app = None, log_file = None, nb_process = 1, max_nb_websockect = 5000, worker_class = None, use_gevent = False, gunicorn_options = None)
Werkzeug backend
The Werkzeug backend supports only Ajax web apps.
from fullpy.server.werkzeug_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000", url_prefix = "", flask_app = None, log_file = None, nb_process = 1, werkzeug_options = None)
Flask backend
The Flask backend supports only Ajax web apps.
It is not exactely a backend: it just create a Flask application for the web app (or add the web app to an existent Flask application). Then, it is up to you to choose any Flask-compatible server (i.e. any WSGI server).
from fullpy.server.werkzeug_backend import *
flask_app = serve_forever([MyWebApp()], "http://127.0.0.1:5000", url_prefix = "", flask_app = None)
Remote function calls (RPC, remote procedure call)
Creating a remotely-callable function
In the Webapp class, a remotely-callable function can be defined by using the @rpc
decorator.
Server-side
Remotely-callable functions defined in the server must be prefixed by server_
.
Their first argument is always the session object (which is None if there is no session), and additional arguments
are allowed.
Here is an example:
class MyWebApp(ServerSideWebapp):
def __init__(self):
[...]
@rpc # Mark the function as remotely callable by the client
def server_remote_function(self, session, argument1, argument2):
return argument1 + argument2
Client-side
Remotely-callable functions defined in the client are supported only if you use WebSockets;
they must be prefixed by client_
. Contrary to server ones, they have no session argument.
Here is an example:
class MyWebApp(ClientSideWebapp):
def __init__(self):
[...]
@rpc # Mark the function as remotely callable by the server
def client_remote_function(self, argument1, argument2):
return argument1 + argument2
Calling a remote function
When calling a a remotely-callable function, the first argument is always a callback function
that will be called with the returned value.
You may pass None
as callback if you don’t need the returned value.
Client-side
In the client, remotely-callable server functions can be called directly on the webapp.
Here are examples, with and without callback:
def done(response):
print(response)
webapp.server_remote_function(done, 2, 3)
webapp.server_remote_function(None, 2, 3)
Server-side
In the server, remotely-callable client functions can be called at three levels:
directly on the webapp: in that case, the function is executed for all connected clients.
on a Session object: in that case, the function is executed for the corresponding client.
on a Group object: in that case, the function is executed for all clients in that Group.
Here are examples:
def done(response):
print(response)
webapp.client_remote_function(done, 2, 3)
session.client_remote_function(done, 2, 3)
group.client_remote_function(done, 2, 3)
Serialization and supported datatypes
FullPy uses its own object serializer for serializing remote functions arguments and return values. It supports all basic Python datatypes (including int, float, str, tuple, list and dict) and can be extended for serializing Python objects and/or OWL ontology entities. It produce a JSON compatible serialization if the encoded data is JSON compatible; however, it also supports non-JSON feature, such as Python tuples and dictionaries with non-string keys.
For more information on the serializer, please refer to Sessions.
WebSocket example
The two next subsections give an example of an Hello World FullPy web application with WebSocket (the code can be found in the “demo/demo_1_hello_world_websocket” directory of FullPy). We have already seen a similar ajax example (see Starting a new project with FullPy).
Hello World server example
from gevent import monkey
monkey.patch_all()
import sys, os, os.path
from fullpy.server import *
class MyWebApp(ServerSideWebapp):
def __init__(self):
ServerSideWebapp.__init__(self)
self.name = "demo"
self.url = "/index.html"
self.title = "FullPy demo"
self.static_folder = os.path.join(os.path.dirname(__file__), "static")
self.use_python_client(os.path.join(os.path.dirname(__file__), "client.py"))
self.use_websocket(debug = True)
@rpc # Mark the function as remotely callable by the client (RPC = remote procedure call)
def server_hello(self, session): # The name of server-side functions MUST starts with "server_"
def f():
gevent.sleep(2.0) # Wait 2 seconds
session.client_update_speech(None, "Goodbye!") # Call the client_update_speech() remote function on the client
gevent.spawn(f) # Execute f() in a separate "greenlet" microthread, in parallel
return "Hello world!"
from fullpy.server.gunicorn_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000")
Hello World client example
from fullpy.client import *
class MyWebApp(ClientSideWebapp):
def on_started(self, url_params):
def done(response):
html = HTML("""FullPy Demo loaded Ok! Server says: '<span id="server_speech">%s</span>'.""" % response)
html.show()
webapp.server_hello(done) # Call the server_hello() remote function on the server
@rpc # Mark the function as remotely callable by the server (RPC = remote procedure call)
def client_update_speech(self, text): # The name of client-side functions MUST starts with "client_"
html = HTML(text)
html.show(container = "server_speech") # Update the content of the "server_speech" HTML tag
MyWebApp()
HTML widget system
Creating HTML pieces
In the client, the HTML class can be used for creating and displaying pieces of HTML, and widgets.
A simple, fixed, piece of HTML code can be created as follows:
html = HTML("""<div>This is a <b>piece</b> of HTML</div>""")
It can also be created over several lines, as follows:
html = HTML()
html << """<div>First part</div>"""
html << """<div>Second part</div>"""
html << """<div id="part3">Third part</div>"""
The bind() method can be used to bind function to Javascript events.
- bind(html_id, event, func)
Bind a callback function to an event, for the given HTML id. Notice that the actual binding may not occur immediately, but when the HTML piece will be displayed in the web browser. For more information on the available events and the
func
argument, please refer to Brython documentation.html_id
: the ID the HTML element.event
: The name of the event.func
: a callable that will be called when the event occur.
Here is an example:
def on_click(event):
print("CLICKED!")
html.bind("part3", click, on_click)
Creating HTML widgets
Finally, you can subclass HTML to create your own widget.
Here is an example:
class MyWidget(HTML):
def build(self, builder):
self << """<div>First part</div>"""
self << """<div>Second part</div>"""
self << """<input id="ok_%s" type="button" value="Ok"></input>""" % id(self)
self.bind("ok_%s" % id(self), "click", self.on_ok)
def on_ok(self, event):
print("OK clicked")
HTML widgets, i.e. subclasses of HTML, can also be inserted inside HTML pieces (including other widgets). For example:
html = HTML()
html << """<div>Plain HTML</div>"""
html << MyWidget()
Displaying an HTML piece
The following methods of HTML objects can be used to display a HTML piece:
- show(container='main_content')
Show the piece of HTML in the web browser.
container
: the HTML ID of the element that will display the HTML piece. By default, it is displayed in the entire HTML page.
- show_replace(replaced_id)
Show the piece of HTML in the web browser, replacing the element of the given ID.
replaced_id
: the HTML ID of the element that will replaced by the HTML piece.
- show_popup(add_close_button=True, allow_close=True, container='popup_window')
Show the piece of HTML in the web browser, in a popup window.
add_close_button
: if True, add a close button “X” at the top-right of the popup window.allow_close
: if True, the popup window is closed when the user click outside the window or press escape.container
: the HTML ID of the node that will used to display the popup window.
Finally, hide_popup()
can be used to close the popup window.
- hide_popup(event=None, container='popup_window')
Hide the current popup window.
event
: No-op argument (only present in order to allow the use of hide_popup() as a callback to Javascript events).container
: the HTML ID of the node that is used to display the popup window.
Refreshing an HTML piece
A common trick is to use show_replace() for refreshing an HTML widget, as in the following example:
class MyRefreshableWidget(HTML):
def build(self, builder):
self << """<div id="widget_%s">""" % id(self)
self << """... [add content of the widget here]"""
self << """</div>"
def refresh(self):
self.show_refresh("widget_%s" % id(self))
Translations support
XXX this documentation is still under construction
Creating a persistent object model Using ontologies
XXX this documentation is still under construction
Please refer to the Owlready2 documentation here :
Sessions
XXX this documentation is still under construction
Groups
XXX this documentation is still under construction
Sessions
XXX this documentation is still under construction
Demos
The demo/
directory in FullPy sources includes several demo web apps.
Multi-room chat
This demo web app is a multi-room, multi-user chat system. It uses:
WebSocket, allowing the server to call the client(s) when new messages are available,
Session, allowing the server to store per-client data (as required by WebSocket),
Group, each group representing a chat room, associated with one or more user sessions,
Ontology quadstore, for data persistence, including chat messages but also sessions and groups.

Server
First, the server app enables GEvent (mandatory for WebSocket) and import GEvent, FullPy and Owlready among other modules:
from gevent import monkey
monkey.patch_all()
import sys, os, os.path, datetime, gevent
from owlready2 import *
from fullpy.server import *
Second, we create a new World for storing persistent data. Data is stored in /tmp/demo_chat.sqlite3
(you may need to adapt the filename according to your system).
We load the FullPy ontology (which define a few classes, like User, Session and Group), and create a new ontology “chat.owl”.
world = World(filename = "/tmp/demo_chat.sqlite3")
fullpy_onto = get_fullpy_onto(world)
chat_onto = world.get_ontology("http://test.org/chat.owl")
Third, within the chat ontology, we subclass User, Session and Group.
The Group subclass is named ChatRoom, it has an internal name (name
attribute, automatically generated by Owlready)
and a user-flrendly label (label
attribute).
- We also create a simple object model for chat:
the
Message
class correspond to a message in the chat, theas_tuple()
method is used to serialize a message as a tuple,the
text
data property indicate the text of a message,the
date
data property indicate the date of a message,the
messages
object property indicate the list of messages associated with a given Group.
with chat_onto:
class MyUser(fullpy_onto.User): pass
class MySession(fullpy_onto.Session): pass
class ChatRoom(fullpy_onto.Group): pass
class Message(Thing):
def as_tuple(self):
return ("%s/%s/%s %s:%s" % (self.date.day, self.date.month, self.date.year,
self.date.hour, self.date.minute), self.user.login, self.text)
class text(Message >> str, FunctionalProperty): pass
class date(Message >> datetime.datetime, FunctionalProperty): pass
class messages(ChatRoom >> Message): pass
Fourth, we create 3 users and 3 chat rooms:
chat_onto.MyUser(login = "user1", password = "123")
chat_onto.MyUser(login = "user2", password = "123")
chat_onto.MyUser(login = "user3", password = "123")
chat_onto.ChatRoom(label = ["Python programming"])
chat_onto.ChatRoom(label = ["Bird watching"])
chat_onto.ChatRoom(label = ["Bike riding"])
Fifth, we create the web app server class by subclassing ServerSideWebapp.
In __init__()
we call use_python_client()
, use_ontology_quadstore()
, use_session()
and use_websocket()
.
class MyWebApp(ServerSideWebapp):
def __init__(self):
ServerSideWebapp.__init__(self)
self.name = "demo_4"
self.title = "FullPy demo"
self.url = "/index.html"
self.static_folder = os.path.join(os.path.dirname(__file__), "..", "static")
self.js = []
self.css = ["demo_4.css"]
self.use_python_client(os.path.join(os.path.dirname(__file__), "client.py"))
self.use_ontology_quadstore(world)
self.use_session(chat_onto.MySession, chat_onto.ChatRoom)
self.use_websocket(debug = True)
Sixth, we define four remotely-callable methods:
server_get_chat_room_names()
: returns a dict mapping chat room names to their labels.
@rpc
def server_get_chat_room_names(self, session):
return { chat_room.name : chat_room.label.first() for chat_room in ChatRoom.instances() }
server_join_chat_room()
: joins the given chat room (indicated by its name).The current client (identified by its session) joins the given chat room (and implicitely leaves the current one). This method calls
session.join_group()
. It returns the list of messages in the chat room.
@rpc
def server_join_chat_room(self, session, chat_room_name):
if session.groups: session.quit_group(session.groups[0])
chat_room = chat_onto[chat_room_name]
session.join_group(chat_room)
return [message.as_tuple() for message in sorted(chat_room.messages, key = lambda message: message.date)]
server_create_chat_room()
: creates a new chat room.This method create a new instance of the ChatRoom class. The new chat room is automatically stored in the ontology quadstore. Finally, it calls
client_new_chat_room()
for all clients, in order to inform them of the existence of the new chat room.
@rpc
def server_create_chat_room(self, session, chat_room_label):
with chat_onto:
chat_room = chat_onto.ChatRoom(label = chat_room_label)
self.client_new_chat_room(None, chat_room.name, chat_room_label)
server_add_message()
: adds a new message in the current session’s chat room.This method create a new instance of the Message class, and add it to the session’s current chat room. Finally, it calls
client_new_message()
for all client in the chat room (remember that ChatRoom inherit from Group, andclient_xxx()
remote functions can be called on a Group).
@rpc
def server_add_message(self, session, text):
chat_room = session.groups[0]
with chat_onto:
message = chat_onto.Message(date = datetime.datetime.now(), user = session.user, text = text)
chat_room.messages.append(message)
chat_room.client_new_message(None, *message.as_tuple())
Seventh, we run the web app with GUnicorn:
from fullpy.server.gunicorn_backend import *
serve_forever([MyWebApp()], "http://127.0.0.1:5000")
Client
First, we import Fullpy:
from fullpy.client import *
from fullpy.client.auth import *
Second, we create the client web app by subclassing ClientSideWebapp. In __init__()
, we create two attributes,
chat_room (the name of the current chat room) and messages (the list of messages in the current chat room).
class MyWebApp(ClientSideWebapp):
def __init__(self):
ClientSideWebapp.__init__(self)
self.chat_room = None
self.messages = []
Third, we override on_session_opened()
. This methods is called when a new session is opened (anonymous or authentified).
If the session is anonymous (i.e. user_login is empty), we show a LoginDialog to prompt user for login.
Otherwise, we call server_get_chat_room_names()
on the server, in order to obtain the dict of available chat rooms.
When the dict is obtained, done()
is called back, and we store the dict and call select_chat_room()
to select
by default the first chat room.
def on_session_opened(self, user_login, user_class, client_data):
if not user_login:
LoginDialog(None).show_popup()
return
def done(chat_room_names):
self.chat_room_names = chat_room_names
self.select_chat_room(sorted(chat_room_names)[0])
self.server_get_chat_room_names(done)
Fourth, we define the select_chat_room()
method. It calls server_join_chat_room()
on the server and, when done,
it stores the chat room name, the current messages returned by the sever, and call create_html()
.
def select_chat_room(self, name):
def done(messages):
self.chat_room = name
self.messages = messages
self.create_html()
self.server_join_chat_room(done, name)
Fifth, we define the create_html()
method. It creates the main HTML interface, which is composed of 3 HTML widgets
(chat room list on the left, message view on the right, and entry box at the bottom right).
It assembles the 3 widgets inside an HTML table, and shows it.
def create_html(self):
self.chat_room_list = ChatRoomList()
self.message_view = MessageView()
self.entry_box = EntryBox()
self.main_html = HTML()
self.main_html << """<table id="chat_table" cellspacing="0"><tr><td>"""
self.main_html << self.chat_room_list
self.main_html << """</td><td>"""
self.main_html << self.message_view
self.main_html << self.entry_box
self.main_html << """</td></tr></table>"""
self.main_html.show()
Sixth, we define the client_new_chat_room()
remotely-callable method.
It is called by the server whenever a new chat room has been created.
In that case, we update the dict of chat room names, and we refresh the chat room list HTML widget.
@rpc
def client_new_chat_room(self, chat_room_name, chat_room_label):
webapp.chat_room_names[chat_room_name] = chat_room_label
webapp.chat_room_list.refresh()
Seventh, we define the client_new_message()
remotely-callable method.
It is called by the server whenever a new message is available in the current chat room.
In that case, we add the message to the list of current messages,
and we call add_message()
on the message view HTML widget.
@rpc
def client_new_message(self, date, user_login, message_text):
webapp.messages.append((date, user_login, message_text))
webapp.message_view.add_message(date, user_login, message_text)
The ChatRoomList HTML widget shows the list of available chat rooms. It also has a “Create new room…” button. When clicked, it creates and shows an instance of the NewRoomDialog HTML widget.
class ChatRoomList(HTML):
def build(self, builder):
self << """<div id="chat_room_list"><div class="title">FullPy Chat rooms :</div>"""
for name, label in sorted(webapp.chat_room_names.items(), key = lambda i: i[1]):
if name == webapp.chat_room:
self << """<div id="chat_room_%s" class="chat_room selected">%s</div>""" % (name, label)
else:
self << """<div id="chat_room_%s" class="chat_room">%s</div>""" % (name, label)
def on_click(event, name = name):
webapp.select_chat_room(name)
self.bind("chat_room_%s" % name, "click", on_click)
self << """<input id="new_room" type="button" value="Create new room..."></input>"""
self.bind("new_room", "click", self.on_new_room)
self << """</div>"""
def on_new_room(self, event): NewRoomDialog().show_popup()
def refresh(self): self.show_replace("chat_room_list")
The NewRoomDialog HTML widget ask the user for the label of the new room.
If the “Ok” button is clicked, it calls the server_create_chat_room()
remotely-callable function.
class NewRoomDialog(HTML):
def build(self, builder):
self << """<h2>Create new chat room :</h2>"""
self << """Room label : <input id="chat_room_label" type="text"></input><br/><br/>"""
self << """<input id="ok" type="button" value="Ok"></input>"""
self.bind("ok", "click", self.on_ok)
def on_ok(self, event):
chat_room_label = document["chat_room_label"].value.strip()
webapp.server_create_chat_room(None, chat_room_label)
hide_popup()
The MessageView HTML widget show the list of messages.
It has a add_message()
method, that is used to add new message.
class MessageView(HTML):
def build(self, builder):
self << """<div id="message_view">"""
if webapp.messages:
for date, user_login, message_text in webapp.messages:
self << self.message_to_html(date, user_login, message_text)
self << """</div>"""
def message_to_html(self, date, user_login, message_text):
if user_login == webapp.user_login:
html = """<div class="message self">"""
else:
html = """<div class="message">"""
html += """<div class="message_header">%s (%s) :</div>""" % (user_login, date)
html += """<div class="message_content">%s</div></div>""" % message_text
return html
def add_message(self, date, user_login, message_text):
document["message_view"].insertAdjacentHTML("beforeend", self.message_to_html(date, user_login, message_text))
The EntryBox HTML widget is an input field that can be used to enter new messages.
When a new message is entered, it call the server_add_message()
remotely-callable function.
class EntryBox(HTML):
def build(self, builder):
self << """<div id="entry_box">"""
self << """<table id="entry_table"><tr><td>Say something:</td>"""
self << """<td id="entry_td"><input id="entry" type="text"></input></td>"""
self << """<td><input id="send" type="button" value="Send"></input></td></tr></table>"""
self << """</div>"""
self.bind("entry", "keypress", self.on_keypress)
self.bind("send", "click", self.on_send)
def on_keypress(self, event):
if event.key == "Enter": self.on_send(event)
def on_send(self, event):
text = document["entry"].value.strip()
if text:
webapp.server_add_message(None, text)
Finally, we instanciate the web app:
MyWebApp()
Contact and links
In case of trouble, please write to the forum or contact Jean-Baptiste Lamy <jean-baptiste.lamy @ univ-paris13 . fr>
LIMICS
University Paris 13, Sorbonne Paris Cité
Bureau 149
74 rue Marcel Cachin
93017 BOBIGNY
FRANCE
FullPy on BitBucket (Git development repository): https://bitbucket.org/jibalamy/fullpy