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判断for循环最后一次的6种方法

《Python判断for循环最后一次的6种方法》在Python中,通常我们不会直接判断for循环是否正在执行最后一次迭代,因为Python的for循环是基于可迭代对象的,它不知道也不关心迭代的内部状态... 目录1.使用enuhttp://www.chinasem.cnmerate()和len()来判断for

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

使用Python合并 Excel单元格指定行列或单元格范围

《使用Python合并Excel单元格指定行列或单元格范围》合并Excel单元格是Excel数据处理和表格设计中的一项常用操作,本文将介绍如何通过Python合并Excel中的指定行列或单... 目录python Excel库安装Python合并Excel 中的指定行Python合并Excel 中的指定列P

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

python管理工具之conda安装部署及使用详解

《python管理工具之conda安装部署及使用详解》这篇文章详细介绍了如何安装和使用conda来管理Python环境,它涵盖了从安装部署、镜像源配置到具体的conda使用方法,包括创建、激活、安装包... 目录pytpshheraerUhon管理工具:conda部署+使用一、安装部署1、 下载2、 安装3

Python进阶之Excel基本操作介绍

《Python进阶之Excel基本操作介绍》在现实中,很多工作都需要与数据打交道,Excel作为常用的数据处理工具,一直备受人们的青睐,本文主要为大家介绍了一些Python中Excel的基本操作,希望... 目录概述写入使用 xlwt使用 XlsxWriter读取修改概述在现实中,很多工作都需要与数据打交