python @gwt_利用GWT和Python的协同作用

2024-03-22 19:40
文章标签 python 作用 协同 gwt

本文主要是介绍python @gwt_利用GWT和Python的协同作用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

python @gwt

借助Google的Web工具包(GWT),您可以完全使用Java™代码使用Ajax开发Rich Internet Application(RIA)。 您可以使用丰富的Java工具集(IDE,重构,代码完成,调试器等)来开发可以部署在所有主要Web浏览器上的应用程序。 使用GWT,您可以编写行为类似于桌面应用程序但在浏览器中运行的应用程序。 Pyjamas是GWT的端口,是用于在Python中开发Ajax应用程序的工具和框架。

睡衣包含一个独立的Python到JavaScript编译器,以及一个Ajax框架和小部件集。 使用这些组件,您可以编写全面的应用程序,而无需编写任何JavaScript。

本文介绍了睡衣的背景,原理,相关工具以及优点,同时向您展示了如何创建一个示例应用程序,该应用程序将存储基本联系信息(姓名,电子邮件地址,电话号码)。 您也可以下载示例应用程序的代码。

本系列的第二部分将说明如何构建自定义的睡衣组件。

背景

Python是最早移植到JVM(Jython),后来又移植到.Net(IronPython)的流行语言之一。 类似于Python的语法已被移植以产生与使用C(Cython)编写程序可比的机器代码。 因此,毫不奇怪,Python是最早的一种语言(在Google用Java语言开创先河之后)被翻译成JavaScript并以跨浏览器的方式运行。

在最近的过去,在Ajax中完成整个应用程序的机会似乎很少。 但是,使用GWT,您可以完全使用Java代码使用Ajax开发RIA。 GWT使您可以编写行为类似于桌面应用程序但在浏览器中运行的应用程序。

相反,Adobe AIR和Silverlight允许Web样式的应用程序在桌面上运行。 Android,Adobe AIR,Google Chrome,Safari和iPhone均使用WebKit进行渲染。 GWT的一个问题是,它不允许您编写作为桌面应用程序运行的应用程序(即使GWT的呈现开发工具集基于WebKit)。

Pajamas具有类似于GWT的Python到JavaScript编译器,以及一组Ajax小部件,这些小部件具有与GWT对应的API相同的API。 (您实际上可以使用GWT文档来开发Pajamas应用程序。)Python具有简洁而强大的语法。 例如,GWT 1.2花费了80,000行代码来编写,而睡衣只花了8,000行来完成相同的任务。

睡衣概述

WebKit,XUL及其类似物为桌面应用程序带来了现代气息。 睡衣将WebKit带给Python开发人员。 借助Webkit,Pajamas成为跨浏览器和跨平台的GUI小部件集。 您可以开发可在任何运行WebKit和XUL的地方运行的小部件。 基于Pajamas API的应用程序可以存在于GWT应用程序所在的任何位置。 另外,Pajamas可让您编写基于WebKit和XUL构建的桌面应用程序。 这比在Qt或GTK上构建应用程序更可取,因为WebKit支持CSS,并且它在许多其他地方用于可靠的呈现(iPhone,Safari,Android等)。 但是,关于Python,XUL和WebKit有点麻烦(请参见侧栏)。

像GWT一样,Pyjamas是一个GUI组件框架。 如果您使用过Swing或GWT,那么睡衣的开发应该会很熟悉。 像大多数GUI框架一样,睡衣也是事件驱动的。

使用睡衣,您可以创建容器,然后将小部件添加到容器中。 这些小部件可以是标签,文本字段,按钮等。 小部件(如按钮)具有事件处理程序,因此您可以侦听按钮中的单击事件。

使用睡衣进行开发很容易,因为您可以使用通常用于Python的调试工具。 示例包括单元测试,打印语句和Python调试器(pdb,命令行调试器)。 您甚至可以使用Eclipse的Python支持进行调试。 请记住,您可以编写作为本机Python应用程序运行的Pajamas应用程序。 您无需将睡衣应用程序转换为JavaScript。 您可以像使用其他任何Python GUI工具包一样使用睡衣。

本文中的示例应用程序的GUI的第一个版本是仅使用从命令行运行的Python开发的。 它甚至没有最初部署到Web上,而是作为桌面应用程序运行。 这对于开发RIA应用程序是一个很大的优势,因为能够轻松调试程序是一个巨大的好处。

当您准备将应用程序部署到Web上时,需要更加小心所包含的库。 通常在浏览器中运行的Pajamas应用程序中使用JavaScript Object Notation(JSON)-RPC服务。

先决条件

要构建本文中的示例应用程序,您需要下载并安装Pyjamas。 这不是一个小任务。 在尝试让Pajamas在Ubuntu上运行并失败后,我放弃了并将其安装在Debian上。 (有传言说睡衣也可以在Windows®上很好地运行。)已安装的版本在Debian上运行良好。 安装过程可能会持续一段时间,因此您应该遵循Pajamas站点上针对您的环境的最新说明(请参阅参考资料 )。

为了构建服务层,使用了MySQL,Apache,mod_python和Python JSON-RPC。

构建示例应用程序

样本联系人管理应用程序存储基本的联系人信息,例如姓名,电子邮件地址和电话号码。 您将从一个简单的创建,读取,更新和删除(CRUD)应用程序开始,然后再添加实际存储。 您可以使用一个带有内存“数据库”的简单Python脚本来完成全部操作。 该示例使用一个服务层,然后用JSON支持的服务层版本替换此内存中服务层版本,该版本使用MySQL将联系信息存储在关系数据库中。

要了解如何编写模拟服务,您必须了解运行时应用程序将如何运行。 JSON服务将被异步调用。 当您将Pajamas应用程序编译为RIA应用程序(HTML和JavaScript代码)时,在进行调用时,Ajax调用将异步返回结果。 因此,在构建模拟服务时,将模拟Ajax库以异步方式回调GUI。 清单1展示了ContactService调用GUI的callback方法,这将在后面显示。 这是为了模拟JSON异步行为,将在以后添加。

清单1.联系服务
class Contact:def __init__(self, name="", email="", phone=""):self.name = nameself.email = emailself.phone = phoneclass ContactService:def __init__(self, callback):self.callback = callbackself.contacts = []def addContact(self, contact):self.contacts.append(contact)self.callback.service_eventAddContactSuccessful()def updateContact(self, contact):self.callback.service_eventUpdateContactSuccessful()def removeContact(self, contact):self.contacts.remove(contact)self.callback.service_eventRemoveContactSuccessful()def listContacts(self):self.callback.service_eventListRetrievedFromService(self.contacts)

Contact类仅表示联系人(姓名,电子邮件,电话号码)。 ContactService仅具有一个内存中列表(不持久存储到磁盘)。 这个简单的类可让您开发GUI,然后稍加修改即可在开发显示逻辑后使用真正的JSON服务测试GUI。

ContactService使用以service_eventXXX开头的方法将服务事件通知给ContactListGUI (在清单2中定义)。

ContactListGUI仅有125行,相当简单,可管理9个GUI小部件。 它还与ContactService合作管理CRUD清单,如清单2所示。

清单2. ContactListGUI
import pyjd # this get stripped out for JavaScript translation
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.Button import Button
from pyjamas.ui.Label import Label
from pyjamas import Windowfrom  pyjamas.ui.Grid import Grid
from  pyjamas.ui.Hyperlink import Hyperlink
from  pyjamas.ui.TextBox import TextBox# Constants
CONTACT_LISTING_ROOT_PANEL = "contactListing"
CONTACT_FORM_ROOT_PANEL = "contactForm"
CONTACT_STATUS_ROOT_PANEL = "contactStatus"
CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar"
EDIT_LINK = 3
REMOVE_LINK = 4#Service code removedclass ContactListGUI:def __init__(self):self.contactService = ContactService(self)self.currentContact = Contact("Rick", "rhightower@gmail.com", "555-555-5555")self.addButton = Button("Add contact", self.gui_eventAddButtonClicked)self.addNewButton = Button("Add new contact", self.gui_eventAddNewButtonClicked)self.updateButton = Button("Update contact", self.gui_eventUpdateButtonClicked)self.nameField = TextBox()self.emailField = TextBox()self.phoneField = TextBox()self.status = Label()self.contactGrid = Grid(2,5)self.contactGrid.addTableListener(self)self.buildForm()self.placeWidgets()self.contactService.listContacts()	def onCellClicked(self, sender, row, cell):print "sender=%s row=%s cell=%s" % (sender, row, cell)self.gui_eventContactGridClicked(row, cell)def onClick(self, sender):if sender == self.addButton:self.gui_eventAddButtonClicked()elif sender == self.addNewButton:self.gui_eventAddNewButtonClicked()elif sender == self.updateButton:self.gui_eventUpdateButtonClicked()def buildForm(self):formGrid = Grid(4,3)formGrid.setVisible(False)formGrid.setWidget(0, 0, Label("Name"))formGrid.setWidget(0, 1, self.nameField);formGrid.setWidget(1, 0, Label("email"))formGrid.setWidget(1, 1, self.emailField)formGrid.setWidget(2, 0, Label("phone"))formGrid.setWidget(2, 1, self.phoneField)formGrid.setWidget(3, 0, self.updateButton)formGrid.setWidget(3, 1, self.addButton)self.formGrid = formGriddef placeWidgets(self):RootPanel(CONTACT_LISTING_ROOT_PANEL).add(self.contactGrid)RootPanel(CONTACT_FORM_ROOT_PANEL).add(self.formGrid)RootPanel(CONTACT_STATUS_ROOT_PANEL).add(self.status)RootPanel(CONTACT_TOOL_BAR_ROOT_PANEL).add(self.addNewButton)def loadForm(self, contact):self.formGrid.setVisible(True)self.currentContact = contactself.emailField.setText(contact.email)self.phoneField.setText(contact.phone)self.nameField.setText(contact.name)def copyFieldDateToContact(self):self.currentContact.email = self.emailField.getText()self.currentContact.name = self.nameField.getText()self.currentContact.phone = self.phoneField.getText()

ContactListGUI init方法调用buildForm方法来创建一个新的表单网格,并在其中填充字段以编辑联系人数据。 然后, init方法调用placeWidgets方法,该方法将contactGridformGridstatusaddNewButton小部件放置在承载此GUI应用程序HTML页面中定义的插槽中。 清单3中对此进行了定义。

图1显示了联系人管理应用程序中使用的小部件的概述。

图1.联系人管理GUI中的小部件
该屏幕显示联系人,电话号码,电子邮件和添加或更新联系人的按钮。
清单3. ContactListGUI GUI事件处理程序
<html><head><meta name="pygwt:module" content="Contacts"><link rel='stylesheet' href='Contacts.css'><title>Contacts</title></head><body bgcolor="white"><script language="javascript" src="bootstrap.js"></script><h1>Contact List Example</h1><table align="center"><tr><td id="contactStatus"></td> </tr><tr><td id="contactToolBar"></td></tr><tr><td id="contactForm"></td></tr><tr><td id="contactListing"></td></tr></table></body>
</html>

常量(例如CONTACT_LISTING_ROOT_PANEL="contactListing" )对应于HTML页面中定义的元素的ID(例如id="contactListing" )。 这使页面设计者可以更好地控制应用程序小部件的布局。

现在已构建了基本应用程序。 下一节将介绍几个常见的使用场景。

在页面加载中显示列表

首次加载示例应用程序的页面时,它将调用ContactListEntryPoint__init__方法。 __init__方法调用ContactServiceDelegatelistContacts方法,该方法异步地调用服务的listContact方法。 模拟的ContactServicelistContact方法调用称为service_eventListRetrievedFromService的服务事件处理程序方法,如清单4所示。

清单4. ContactListGUI:调用listContact事件处理程序
class ContactListGUI:…def service_eventListRetrievedFromService(self, results):self.status.setText("Retrieved contact list")self.contacts = results;self.contactGrid.clear();self.contactGrid.resizeRows(len(self.contacts))row = 0for contact in results:self.contactGrid.setWidget(row, 0, Label(contact.name))self.contactGrid.setWidget(row, 1, Label (contact.phone))self.contactGrid.setWidget(row, 2, Label (contact.email))self.contactGrid.setWidget(row, EDIT_LINK, Hyperlink("Edit", None))self.contactGrid.setWidget(row, REMOVE_LINK, Hyperlink("Remove", None))row += 1

service_eventListRetrievedFromService事件处理程序方法存储服务器发送的联系人列表。 然后:

  • 清除显示联系人列表的contactGrid
  • 调整行数以匹配从服务器返回的联系人列表的大小。
  • 遍历联系人列表,将每个联系人的姓名,电话和电子邮件数据放入每行的前三列。
  • 为每个联系人提供“编辑”链接和“删除”链接,使用户可以轻松地删除和编辑联系人。

编辑现有联系人

当用户单击联系人列表中的Edit链接时,将gui_eventContactGridClicked ,如清单5所示。

清单5. ContactListGUI的gui_eventContactGridClicked事件处理程序方法
class ContactListGUI:…def gui_eventContactGridClicked(self, row, col):contact = self.contacts[row]self.status.setText("Name was " + contact.name + " clicked ")if col==EDIT_LINK:self.addNewButton.setVisible(False)self.updateButton.setVisible(True)self.addButton.setVisible(False)self.loadForm(contact)elif (col==REMOVE_LINK):self.contactService.removeContact(contact)…def loadForm(self, contact):self.formGrid.setVisible(True)self.currentContact = contactself.emailField.setText(contact.email)self.phoneField.setText(contact.phone)self.nameField.setText(contact.name)

gui_eventContactGridClicked方法通过找出被单击的列来确定是否已单击“编辑”链接或“删除”链接。 然后,它隐藏addNewButtonaddButton ,并使updateButton可见。 updateButton显示在formGrid并允许用户将更新信息发送回ContactServicegui_eventContactGridClicked然后调用loadForm (如清单5所示),它:

  • formGrid设置为可见。
  • 设置正在编辑的联系人。
  • 将联系人属性复制到emailFieldphoneFieldnameField小部件中。

当用户单击Update按钮时,将gui_eventUpdateButtonClicked事件处理程序方法,如清单6所示。该方法:

  • 使addNewButton可见,以便用户可以添加新联系人。
  • 隐藏formGrid
  • 调用copyFieldDateToContact ,后者将emailFieldphoneFieldnameField小部件中的文本复制回currentContact的属性中。
  • 调用ContactServiceDelegate updateContact方法将新更新的联系人传递回服务。
清单6. ContactListGUI的gui_eventUpdateButtonClicked事件处理程序方法
class ContactListGUI:…def gui_eventUpdateButtonClicked(self, sender):self.addNewButton.setVisible(True)self.formGrid.setVisible(False)self.copyFieldDateToContact()self.contactService.updateContact(self.currentContact)def copyFieldDateToContact(self):self.currentContact.email = self.emailField.getText()self.currentContact.name = self.nameField.getText()self.currentContact.phone = self.phoneField.getText()

上面的两种情况说明了应用程序的工作方式,以及如何利用App Engine for Java提供的基础结构。 清单7显示了ContactListGUI的其余GUI事件处理程序, 清单8显示了其余的服务回调处理程序。

清单7. ContactListGUI的gui_eventUpdateButtonClicked事件处理程序方法
class ContactListGUI:…def gui_eventContactGridClicked(self, row, col):contact = self.contacts[row]self.status.setText("Name was " + contact.name + " clicked ")if col==EDIT_LINK:self.addNewButton.setVisible(False)self.updateButton.setVisible(True)self.addButton.setVisible(False)self.loadForm(contact)elif (col==REMOVE_LINK):self.contactService.removeContact(contact)def gui_eventAddButtonClicked(self, sender):self.addNewButton.setVisible(True)self.formGrid.setVisible(False)self.copyFieldDateToContact()self.contactService.addContact(self.currentContact)def gui_eventUpdateButtonClicked(self, sender):self.addNewButton.setVisible(True)self.formGrid.setVisible(False)self.copyFieldDateToContact()self.contactService.updateContact(self.currentContact)def gui_eventAddNewButtonClicked(self, sender):self.addNewButton.setVisible(False)self.updateButton.setVisible(False)self.addButton.setVisible(True)self.loadForm(Contact())
清单8. ContactListGUI服务回调方法
class ContactListGUI:…def service_eventListRetrievedFromService(self, results):self.status.setText("Retrieved contact list")self.contacts = results;self.contactGrid.clear();self.contactGrid.resizeRows(len(self.contacts))row = 0for contact in results:self.contactGrid.setWidget(row, 0, Label(contact.name))self.contactGrid.setWidget(row, 1, Label (contact.phone))self.contactGrid.setWidget(row, 2, Label (contact.email))self.contactGrid.setWidget(row, EDIT_LINK, Hyperlink("Edit", None))self.contactGrid.setWidget(row, REMOVE_LINK, Hyperlink("Remove", None))row += 1def service_eventAddContactSuccessful(self):self.status.setText("Contact was successfully added")self.contactService.listContacts()def service_eventUpdateContactSuccessful(self):self.status.setText("Contact was successfully updated")self.contactService.listContacts()def service_eventRemoveContactSuccessful(self):self.status.setText("Contact was removed")self.contactService.listContacts()

编译示例

您可以编译此示例应用程序,然后在任何现代浏览器中本机运行。 但是,尝试调试在浏览器中运行的RIA应用程序并不有趣。 幸运的是,您可以使用Pyjamas-Desktop将整个应用程序作为本地Python应用程序运行,如清单9所示。

清单9.运行Pyjamas-Desktop
import pyjd # this get stripped out for JavaScript translation
...
if __name__ == '__main__':pyjd.setup("public/Contacts.html")contacts = ContactListGUI()
pyjd.run()

清单9中的代码实例化了Python桌面应用程序,然后通过调用run方法启动桌面。 当您将此应用程序作为桌面应用程序运行时,可以使用支持可视调试的pdb或Python IDE对其进行调试。

我在主目录下的工具目录中安装了睡衣。 使用Python调试器时,请确保将Pajamas和Pyjamas-Desktop库添加到路径中,如清单10所示。

清单10.将睡衣添加到PYTHONPATH
export PYTHONPATH=/home/rick/tools/pyjamas:/home/rick/tools/pyjamas/library

完成编写应用程序后,您可以运行pyjsbuild将应用程序编译为HTML,JavaScript和JSON-RPC。 清单11显示了运行pyjsbuild的示例脚本。

清单11. build.sh
#!/bin/shoptions="$*"
#if [ -z $options ] ; then options="-O";fi
~/tools/pyjamas/bin/pyjsbuild --print-statements $options Contacts.py

编译应用程序时,您要做的就是由Web服务器托管/ output文件夹。 该示例使用了全新的Debian安装,因此apache2和mod_python是使用apt-get安装的,如清单12所示。

清单12.安装apache2和mod_python
$sudo apt-get install apache2 libapache2-mod-python

在下一个版本的联系人列表中将使用mod_python。 该示例应用程序是在/ home / rick / tools / pyjamas / examples / contact1下创建的。 要将其托管在Apache上,请将以下代码添加到Apache httpd.conf文件中(在Debian上,此文件安装在/ etc / apache2下)。

清单13. /etc/apache2/httpd.conf
Alias /pj "/home/rick/tools/pyjamas" 
<Directory "/home/rick/tools/pyjamas">Options Indexes FollowSymLinks MultiViewsAllowOverride NoneOrder deny,allowallow from all
</Directory>

添加JSON-RPC支持

在使GUI逻辑正常工作之后,该开始对用Python实现的JSON-RPC服务进行编程了。 JSON-RPC是标准; 您可以使用任何编程语言来实现服务器端。 这样,可以将Pajamas前端应用程序安装到具有JSON-RPC后端Web服务的现有项目中。 JSON是一种数据交换格式。 它使用两种结构:

  • 名称/值对的集合(Python中的字典,Java代码中的哈希表或Perl的关联数组)
  • 数组

JSON-RPC是一种远程过程调用协议,该协议使用JSON编码和封送参数和返回类型。 JSON-RPC项目具有适用于Python的绑定。 Twisted,Django和许多其他Python框架也支持JSON-RPC。 清单14显示了一种获取JSON-RPC的简单方法。

清单14.安装JSON-RPC
$ svn checkout http://svn.json-rpc.org/trunk/python-jsonrpc$ cd python-jsonrpc
$ python setup.py install

要编写JSON-RPC服务,您可以使用@ServiceMethod注释方法调用,然后公开一个名为service的模块变量,该变量指向要使用JSON-RPC公开的实例。 清单15显示了一个示例。

清单15. ContactService:联系人列表的JSON-RPC服务
import logginglogging.basicConfig(filename="/tmp/contactjson.log",level=logging.DEBUG)logging.debug("Loading contact service")from jsonrpc import ServiceMethoduse_mysql=Trueif use_mysql:import MySQLdb as db_apilogging.debug("Using mysql")
else:import sqlite3 as db_apilogging.debug("Using sqllite3")db_url = "/tmp/contacts"class ContactService:@ServiceMethoddef test(self):logging.info("Test called")return "test"def connection(self):if use_mysql:connection =  db_api.connect(passwd="mypassword", db="contactdb", user="root")else:connection =  db_api.connect(db_url)return connectiondef run_update(self, func):connection = self.connection()cursor = connection.cursor()try:func(cursor)cursor.close()connection.commit()except Exception, e:connection.rollback()logging.exception("problem handling update")raise efinally:connection.close()def run_query(self, func):connection = self.connection()cursor = connection.cursor()lst = Nonetry:func(cursor)lst = cursor.fetchall()cursor.close()except Exception, e:logging.exception("problem handling query")raise efinally:connection.close()return lst@ServiceMethoddef addContact(self, contact):logging.debug("Add contact called %s", `contact`)def ac(cursor):if use_mysql:cursor.execute(""" insert into contact (name, phone, email) values (%(name)s, %(phone)s, %(email)s) """, contact)else:cursor.execute(""" insert into contact (id, name, phone, email) values (NULL, :name, :phone, :email) """, contact)self.run_update(ac)@ServiceMethoddef updateContact(self, contact):logging.debug("Update contact called %s", `contact`)def uc(cursor):if use_mysql:cursor.execute(""" update contact set name = %(name)s, email = %(email)s, phone = %(phone)swhere id=%(id)s;""", contact)else:cursor.execute(""" update contact set name = :name, email = :email, phone = :phonewhere id=:id;""", contact)self.run_update(uc)@ServiceMethoddef removeContact(self, contact):logging.debug("Remove contact called %s", `contact`)def uc(cursor):if use_mysql:cursor.execute("delete from contact where id=%(id)s;", contact)else:cursor.execute("delete from contact where id=:id;", contact)self.run_update(uc)@ServiceMethoddef listContacts(self):logging.debug("list contact called")def lc(cursor):cursor.execute("select name, phone, email, id from contact")lst = self.run_query(lc)def toMap(x):return {"name":x[0],"phone": x[1], "email":x[2], "id":x[3]}return map(toMap, lst)service = ContactService()#If you can't get mod_python working 
# you can use CGI with the following line.
#handleCGI(service)
# You have to import handleCGI from jsonrpc

清单15可以使用易于安装的MySQL或Python附带的sqlite3。 要使用sqlite3,请将use_mysql设置为False。

清单16显示了测试该服务的单元测试,这对于开发示例应用程序是必不可少的。 清单显示了单元测试使用的实用程序类。

清单16. TestContactService
import unittest
from contacts import ContactService
from dbscript import *class TestContactService(unittest.TestCase):def setUp(self):self.cs = ContactService()try:drop_table()except:print "unable to drop contact table"try:create_table()        except:print "unable to create contact table"def testAdd(self):clear_table()cs = self.cscs.addContact({"name":"Richard","phone":"5205551212","email":"rick@rick.com"})list = cs.listContacts()print listfound = Falsefor cdict in list:if cdict["name"]=="Richard": found = Trueself.assertTrue(found)def testUpdate(self):cs = self.csinsert_test_data()cs.updateContact({"name":"Richard","phone":"5205551212","email":"rick@rick.com","id":1})list = cs.listContacts()print listfound = 0for cdict in list:if cdict["name"]=="Richard": found +=1self.assertTrue(found==1)def testRemove(self):cs = self.csinsert_test_data()cs.removeContact({"name":"Richard","phone":"5205551212","email":"rick@rick.com","id":1})list = cs.listContacts()print listfound = 0for cdict in list:if cdict["name"]=="Richard": found +=1self.assertTrue(found==0)if __name__ == '__main__':unittest.main()

dbscript.py清单17中可以建立无论是MySQLdb的联系人表或sqlite3的联系人表。

清单17. Dbscript,创建,删除,填充联系人
use_mysql = Trueif use_mysql:import MySQLdb as db_api
else:import sqlite3 as db_apidb_url = "/tmp/contacts"create_table_sql = """ 
create table contact (id INTEGER %s PRIMARY KEY, name VARCHAR(50), phone VARCHAR(50), email VARCHAR(50));
"""if use_mysql:create_table_sql = create_table_sql % ("AUTO_INCREMENT",)
else:create_table_sql = create_table_sql % ("",)def run_script(func):if use_mysql:connection =  db_api.connect(passwd="mypassword", db="contactdb", user="root")else:connection =  db_api.connect(db_url)cursor = connection.cursor()try:func(cursor)connection.commit()cursor.close()finally:connection.close()def create_table():def ct(cursor):cursor.execute(create_table_sql)run_script(ct)def drop_table():def dt(cursor):cursor.execute("drop table contact;")run_script(dt)def clear_table():def dt(cursor):cursor.execute("delete from contact;")run_script(dt)def insert_test_data():def itd(cursor):if use_mysql:cursor.execute("insert into contact (id, name, phone, email) values (NULL, 'Bob', '5', 'b@b.com');") cursor.execute("insert into contact (id, name, phone, email) values (NULL, 'Rick', '5', 'b@b.com');")cursor.execute("insert into contact (id, name, phone, email) values (NULL, 'Sam', '5', 'b@b.com');")else:cursor.executescript(""" insert into contact (id, name, phone, email) values (NULL, "Bob", "5", "b@b.com"); insert into contact (id, name, phone, email) values (NULL, "Rick", "5", "b@b.com"); insert into contact (id, name, phone, email) values (NULL, "Sam", "5", "b@b.com"); """)run_script(itd)

本质上, dbscript创建并删除联系人表,并使用单元测试使用的测试数据填充该表。 完成JSON-RPC服务之后,您可以通过将清单18中所示的代码添加到httpd.conf文件中来安装要由Apache HTTPD提供的服务。

清单18. /etc/apache2/httpd.conf
Alias /services "/home/rick/services" <Location /services/>AddHandler mod_python .pyPythonHandler jsonrpc
</Location>

请记住,对服务进行更改后,需要重新启动它,如清单19所示。

清单19.重新启动Apache2以获取对mod_python的更改
$sudo /etc/init.d/apache2 restart

在Pyjamas中运行JSON-RPC代理时,您会得到讨厌的递归错误。 为了帮助调试错误,我使用了JSON-RPC独立客户端lib,如清单20所示。

清单20. Python JSON-RPC客户端
from jsonrpc import ServiceProxy, JSONRPCExceptioncs = ServiceProxy("http://localhost/services/contacts.py")if cs.test()=="test":print "connected"try:cs.addContact({"name":"Larry Wall", "phone":"5551212", "email":"rick@rick.com"})except Exception, e:print e.errorprint `e.error`

上一步是测试和调试中的重要一步。 睡衣的开发还有些新生,因此最好有另一种方法来从另一个来源测试JSON-RPC。

该示例仅将ContactService更改为使用JSONProxy。 JSONProxy是Pajamas客户端对JSON-RPC的支持。 您可以为刚刚编写的服务创建一个代理对象,如清单21中的ContactsJSONProxy所示。JSON服务的返回对象是异步返回的。 因此,当您在JSON代理上进行调用时,您将传递一个ContactService实例,该实例实现onRemoteResponse以异步地从该服务获取响应。

清单21. JSONRPC格式的联系人清单
from pyjamas.JSONService import JSONProxy
...
class Contact:def __init__(self, name="", email="", phone="", id=None):self.name = nameself.email = emailself.phone = phoneself.id = iddef to_dict(self):return {"name":self.name, "email":self.email, "phone":self.phone, "id":self.id}class ContactsJSONProxy(JSONProxy):def __init__(self):JSONProxy.__init__(self, "/services/contacts.py", ["addContact", "removeContact", "updateContact", "listContacts","test"])class ContactService:def __init__(self, callback):self.callback = callbackself.proxy = ContactsJSONProxy()def test(self):self.proxy.test(self)def addContact(self, contact):self.callback.showStatus("Add contact called")self.proxy.addContact(contact.to_dict(), self)def updateContact(self, contact):self.callback.showStatus("Update contact was called")self.proxy.updateContact(contact.to_dict(), self)def removeContact(self, contact):self.callback.showStatus("Remove contact was called")self.proxy.removeContact(contact.to_dict(), self)def listContacts(self):self.proxy.listContacts(self)def onRemoteResponse(self, response, request_info):        if request_info.method == "addContact":self.callback.service_eventAddContactSuccessful()elif request_info.method == "updateContact":self.callback.service_eventUpdateContactSuccessful()elif request_info.method == "listContacts":def toContact(x):return Contact(x["name"], x["email"], x["phone"], x["id"])  contacts = map(toContact, response)self.callback.service_eventListRetrievedFromService(contacts)elif request_info.method == "removeContact":self.callback.service_eventRemoveContactSuccessful()else:self.callback.showStatus(""" REQ METHOD = %s RESP %s """ %(request_info.method,response)) def onRemoteError(self, code, errobj, request_info):message = errobj['message']if code != 0:self.callback.showStatus("HTTP error %d: %s" % (code, message))else:json_code = errobj['code']self.callback.showStatus("JSONRPC Error %s: %s" % (json_code, message))

客户端代码的其余部分与以前的方式非常相似,只有一些外观上的更改。 令人惊奇的是,使用真正的远程RPC服务的客户端与使用独立服务的版本没有太大区别。 这使您可以快速开发GUI,然后仅插入JSON-RPC服务,该服务是单独开发和调试的。

摘要

在“睡衣简介”系列的第一部分中,您探索了睡衣背后的历史和愿景。 您还学习了如何使用Pyjamas,mod_python和Python JSON-RPC创建基于Pyjamas的应用程序。 请继续关注本系列的第2部分,它将介绍如何构建自定义的睡衣组件。

致谢

特别感谢Luke Kenneth Casson Leighton审阅本文并提供了重要的反馈。 他还帮助示例运行,并提供了有关如何调试应用程序的建议。


翻译自: https://www.ibm.com/developerworks/web/library/wa-aj-pyjamas/index.html

python @gwt

这篇关于python @gwt_利用GWT和Python的协同作用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/836064

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

nudepy,一个有趣的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个有趣的 Python 库 - nudepy。 Github地址:https://github.com/hhatto/nude.py 在图像处理和计算机视觉应用中,检测图像中的不适当内容(例如裸露图像)是一个重要的任务。nudepy 是一个基于 Python 的库,专门用于检测图像中的不适当内容。该

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

业务协同平台--简介

一、使用场景         1.多个系统统一在业务协同平台定义协同策略,由业务协同平台代替人工完成一系列的单据录入         2.同时业务协同平台将执行任务推送给pda、pad等执行终端,通知各人员、设备进行作业执行         3.作业过程中,可设置完成时间预警、作业节点通知,时刻了解作业进程         4.做完再给你做过程分析,给出优化建议         就问你这一套下

HTML提交表单给python

python 代码 from flask import Flask, request, render_template, redirect, url_forapp = Flask(__name__)@app.route('/')def form():# 渲染表单页面return render_template('./index.html')@app.route('/submit_form',

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使