Removes python client. https://reviews.apache.org/r/45976
diff --git a/.gitignore b/.gitignore
index 267def7..a9cec8c 100755
--- a/.gitignore
+++ b/.gitignore
@@ -20,7 +20,6 @@
*/gwt-unitCache
war/WEB-INF
*.old
-*.log*
.DS_Store
### IntelliJ Idea
@@ -50,7 +49,6 @@
### Generated Sources
**/generated/
### Gwt Testing
-wave/gwt-unitCache
### config
wave/local.net.cfg.lua
wave/config/wave.conf
diff --git a/LICENSE b/LICENSE
index a2be50d..6bf7059 100644
--- a/LICENSE
+++ b/LICENSE
@@ -201,52 +201,6 @@
limitations under the License.
***THE FOLLOWING LICENSE APPLIES TO***
-- The OAuth python library located at /src/python/api/oauth/
- Copyright (c) 2007 Andy Smith
-
-The MIT License
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-***THE FOLLOWING LICENSE APPLIES TO***
-- SimpleJSON located at /src/python/api/simplejson/
- Copyright (c) 2006 Bob Ippolito
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-***THE FOLLOWING LICENSE APPLIES TO***
- Protobuf Descriptors located at /src/google/protobuf/descriptor.proto
Protocol Buffers - Google's data interchange format
diff --git a/wave/dist/CHANGES b/wave/dist/CHANGES
index cf47949..a2d6c8e 100644
--- a/wave/dist/CHANGES
+++ b/wave/dist/CHANGES
@@ -437,147 +437,6 @@
Date: Sat Jan 18 16:06:56 2014 +0200
Merge branch 'wave-0.4-release' into master
-
- Conflicts:
- .classpath
- .gitignore
- .project_template
- CHANGES
- NOTICE
- README
- build.properties
- build.xml
- jaas.config
- prosody.cfg.lua.example
- proto_src/org/waveprotocol/box/attachment/AttachmentProto.java
- proto_src/org/waveprotocol/box/server/persistence/protos/ProtoAccountStoreData.java
- run-server.bat
- server-config.xml
- server.federation.config
- src/com/google/gwt/build.xml
- src/com/google/gwt/websockets/WebSockets.gwt.xml
- src/com/google/wave/api/data/Data.gwt.xml
- src/com/google/wave/api/robot/Robot.gwt.xml
- src/org/waveprotocol/box/server/frontend/WaveletInfo.java
- src/org/waveprotocol/box/server/persistence/protos/delta-store.proto
- src/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImpl.java
- src/org/waveprotocol/box/server/waveserver/Wave.java
- src/org/waveprotocol/box/webclient/search/i18n/SearchPresenterMessages_sl.properties
- src/org/waveprotocol/box/webclient/search/i18n/SearchWidgetMessages_sl.properties
- src/org/waveprotocol/box/webclient/search/mock/search.html
- src/org/waveprotocol/protobuf/build.xml
- src/org/waveprotocol/pst/templates/api/properties
- src/org/waveprotocol/pst/templates/builder/properties
- src/org/waveprotocol/pst/templates/gson/properties
- src/org/waveprotocol/pst/templates/jso/properties
- src/org/waveprotocol/pst/templates/pojo/properties
- src/org/waveprotocol/pst/templates/proto/properties
- src/org/waveprotocol/pst/templates/util/properties
- src/org/waveprotocol/wave/client/build.xml
- src/org/waveprotocol/wave/client/common/build.xml
- src/org/waveprotocol/wave/client/scheduler/build.xml
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/color/ComplexColorPicker.css
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/color/SimpleColorPicker.css
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/color/i18n/ColorPickerMessages_en.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/color/i18n/ColorPickerMessages_es.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/i18n/LinkerMessages_fr.properties
- src/org/waveprotocol/wave/common/build.xml
- src/org/waveprotocol/wave/communication/build.xml
- src/org/waveprotocol/wave/concurrencycontrol/build.xml
- src/org/waveprotocol/wave/crypto/build.xml
- src/org/waveprotocol/wave/diff/build.xml
- src/org/waveprotocol/wave/federation/build.xml
- src/org/waveprotocol/wave/media/build.xml
- src/org/waveprotocol/wave/model/build.xml
- src/org/waveprotocol/wave/testing/build.xml
- src/org/waveprotocol/wave/util/build.xml
- src/python/api/__init__.py
- src/python/api/appengine_robot_runner.py
- src/python/api/blip.py
- src/python/api/blip_test.py
- src/python/api/commandline_robot_runner.py
- src/python/api/commandline_robot_runner_test.py
- src/python/api/django_oauth.py
- src/python/api/element.py
- src/python/api/element_test.py
- src/python/api/errors.py
- src/python/api/events.py
- src/python/api/module_test_runner.py
- src/python/api/ops.py
- src/python/api/ops_test.py
- src/python/api/robot.py
- src/python/api/robot_test.py
- src/python/api/run_unit_tests.py
- src/python/api/search.py
- src/python/api/search_test.py
- src/python/api/testdata.py
- src/python/api/util.py
- src/python/api/util_test.py
- src/python/api/wavelet.py
- src/python/api/wavelet_test.py
- src/python/api/waveservice.py
- src/python/api/waveservice_test.py
- test/org/waveprotocol/wave/concurrencycontrol/Tests.gwt.xml
- test/org/waveprotocol/wave/model/experimental/schema/bad1.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad10.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad11.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad12.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad13.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad14.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad15.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad16.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad17.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad18.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad19.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad2.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad20.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad21.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad22.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad23.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad24.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad3.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad4.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad5.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad6.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad7.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad8.schema
- test/org/waveprotocol/wave/model/experimental/schema/bad9.schema
- test/org/waveprotocol/wave/model/experimental/schema/good1.schema
- test/org/waveprotocol/wave/model/experimental/schema/good10.schema
- test/org/waveprotocol/wave/model/experimental/schema/good11.schema
- test/org/waveprotocol/wave/model/experimental/schema/good12.schema
- test/org/waveprotocol/wave/model/experimental/schema/good13.schema
- test/org/waveprotocol/wave/model/experimental/schema/good14.schema
- test/org/waveprotocol/wave/model/experimental/schema/good15.schema
- test/org/waveprotocol/wave/model/experimental/schema/good16.schema
- test/org/waveprotocol/wave/model/experimental/schema/good2.schema
- test/org/waveprotocol/wave/model/experimental/schema/good3.schema
- test/org/waveprotocol/wave/model/experimental/schema/good4.schema
- test/org/waveprotocol/wave/model/experimental/schema/good5.schema
- test/org/waveprotocol/wave/model/experimental/schema/good6.schema
- test/org/waveprotocol/wave/model/experimental/schema/good7.schema
- test/org/waveprotocol/wave/model/experimental/schema/good8.schema
- test/org/waveprotocol/wave/model/experimental/schema/good9.schema
- test/org/waveprotocol/wave/model/supplement/tests.gwt.xml
- test/org/waveprotocol/wave/model/tests.gwt.xml
- third_party/codegen/gwt/README.google
- third_party/codegen/gwt/gwt-dev.jar
- third_party/codegen/gwt/gwt-user.jar
- third_party/runtime/bouncycastle/LICENSE.html
- third_party/runtime/gson/LICENSE
- third_party/runtime/guava/README.google
- third_party/runtime/jetty/README.google
- third_party/runtime/libidn/LICENSE
- third_party/runtime/libidn/README.google
- third_party/runtime/libidn/libidn-1.26.jar
- third_party/runtime/mongo-driver/README.google
- third_party/runtime/protobuf/README.google
- third_party/test/jmock/README.google
- third_party/test/mockito/README.google
- third_party/test/mockito/mockito-all-1.9.5.jar
- tools/eclipse-launch/src.org.waveprotocol.wave.client.editor.examples.img.Img_Example-launch
- tools/eclipse-launch/src.org.waveprotocol.wave.client.editor.harness.EditorTest-launch
- war/.gitignore
commit 0b550672da3c923fbc6be0a963d2bf54c735b783
Author: Yuri Zelikov <vega113@gmail.com>
@@ -911,45 +770,6 @@
Date: Tue Aug 27 19:14:34 2013 +0000
Merge branch 'trunk' into 0.4-release-staging
-
- Conflicts:
- README
- src/org/waveprotocol/box/waveimport/WaveExport.java
- src/org/waveprotocol/box/waveimport/WaveImport.java
- src/org/waveprotocol/box/waveimport/google/RobotApi.java
- src/org/waveprotocol/box/waveimport/google/RobotSearchDigest.java
- src/org/waveprotocol/box/waveimport/google/RobotSearchDigestGsonImpl.java
- src/org/waveprotocol/box/waveimport/google/RobotSearchDigestImpl.java
- src/org/waveprotocol/box/waveimport/google/RobotSearchDigestUtil.java
- src/org/waveprotocol/box/waveimport/google/oauth/NeedNewOAuthTokenException.java
- src/org/waveprotocol/box/waveimport/google/oauth/OAuthCredentials.java
- src/org/waveprotocol/box/waveimport/google/oauth/OAuthRequestHelper.java
- src/org/waveprotocol/box/waveimport/google/oauth/OAuthedFetchService.java
- src/org/waveprotocol/box/waveimport/google/oauth/StableUserId.java
- src/org/waveprotocol/box/waveimport/google/oauth/UserContext.java
- src/org/waveprotocol/box/webclient/client/i18n/SessionMessages_es.properties
- src/org/waveprotocol/box/webclient/client/i18n/SessionMessages_fr.properties
- src/org/waveprotocol/box/webclient/client/i18n/WebClientMessages_es.properties
- src/org/waveprotocol/box/webclient/client/i18n/WebClientMessages_fr.properties
- src/org/waveprotocol/box/webclient/search/i18n/SearchPresenterMessages_sl.properties
- src/org/waveprotocol/box/webclient/search/i18n/SearchWidgetMessages_es.properties
- src/org/waveprotocol/box/webclient/search/i18n/SearchWidgetMessages_sl.properties
- src/org/waveprotocol/box/webclient/widget/error/i18n/ErrorMessages_es.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/edit/i18n/ActionMessages_es.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/menu/i18n/MenuMessages_fr.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/menu/i18n/MenuMessages_sl.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/attachment/i18n/AttachmentMessages_fr.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/attachment/i18n/AttachmentMessages_sl.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/gadget/i18n/GadgetCategoryMessages_es.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/gadget/i18n/GadgetSelectorMessages_sl.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/i18n/LinkerMessages_es.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/i18n/LinkerMessages_fr.properties
- src/org/waveprotocol/wave/client/wavepanel/impl/toolbar/i18n/ToolbarMessages_fr.properties
- src/org/waveprotocol/wave/client/wavepanel/view/dom/full/i18n/ReplyBoxMessages_es.properties
- src/org/waveprotocol/wave/client/wavepanel/view/dom/full/i18n/ReplyBoxMessages_fr.properties
- src/org/waveprotocol/wave/client/widget/profile/i18n/ProfilePopupMessages_es.properties
- src/org/waveprotocol/wave/migration/build.xml
-
git-svn-id: https://svn.apache.org/repos/asf/incubator/wave/branches/wave-0.4-release@1517927 13f79535-47bb-0310-9956-ffa450edef68
commit 615d44352fccf53db12bc43829d7d7f2c2155410
diff --git a/wave/src/main/java/python/api/__init__.py b/wave/src/main/java/python/api/__init__.py
deleted file mode 100644
index a92a86c..0000000
--- a/wave/src/main/java/python/api/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Declares the api package."""
diff --git a/wave/src/main/java/python/api/appengine_robot_runner.py b/wave/src/main/java/python/api/appengine_robot_runner.py
deleted file mode 100644
index 776f825..0000000
--- a/wave/src/main/java/python/api/appengine_robot_runner.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""A module to run wave robots on app engine."""
-
-
-import logging
-import sys
-
-import events
-
-from google.appengine.api import urlfetch
-from google.appengine.ext import webapp
-from google.appengine.ext.webapp.util import run_wsgi_app
-
-
-class CapabilitiesHandler(webapp.RequestHandler):
- """Handler to forward a request ot a handler of a robot."""
-
- def __init__(self, method, contenttype):
- """Initializes this handler with a specific robot."""
- self._method = method
- self._contenttype = contenttype
-
- def get(self):
- """Handles HTTP GET request."""
- self.response.headers['Content-Type'] = self._contenttype
- self.response.out.write(self._method())
-
-class ProfileHandler(webapp.RequestHandler):
- """Handler to forward a request ot a handler of a robot."""
-
- def __init__(self, method, contenttype):
- """Initializes this handler with a specific robot."""
- self._method = method
- self._contenttype = contenttype
-
- def get(self):
- """Handles HTTP GET request."""
- self.response.headers['Content-Type'] = self._contenttype
- # Respond with proxied profile if name specified
- if self.request.get('name'):
- self.response.out.write(self._method(self.request.get('name')))
- else:
- self.response.out.write(self._method())
-
-class RobotEventHandler(webapp.RequestHandler):
- """Handler for the dispatching of events to various handlers to a robot.
-
- This handler only responds to post events with a JSON post body. Its primary
- task is to separate out the context data from the events in the post body
- and dispatch all events in order. Once all events have been dispatched
- it serializes the context data and its associated operations as a response.
- """
-
- def __init__(self, robot):
- """Initializes self with a specific robot."""
- self._robot = robot
-
- def get(self):
- """Handles the get event for debugging.
-
- This is useful for debugging but since event bundles tend to be
- rather big it often won't fit for more complex requests.
- """
- ops = self.request.get('events')
- if ops:
- self.request.body = events
- self.post()
-
- def post(self):
- """Handles HTTP POST requests."""
- json_body = self.request.body
- if not json_body:
- # TODO(davidbyttow): Log error?
- return
-
- # Redirect stdout to stderr while executing handlers. This way, any stray
- # "print" statements in bot code go to the error logs instead of breaking
- # the JSON response sent to the HTTP channel.
- saved_stdout, sys.stdout = sys.stdout, sys.stderr
-
- json_body = unicode(json_body, 'utf8')
- logging.info('Incoming: %s', json_body)
- json_response = self._robot.process_events(json_body)
- logging.info('Outgoing: %s', json_response)
-
- sys.stdout = saved_stdout
-
- # Build the response.
- self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
- self.response.out.write(json_response.encode('utf-8'))
-
-
-def operation_error_handler(event, wavelet):
- """Default operation error handler, logging what went wrong."""
- if isinstance(event, events.OperationError):
- logging.error('Previously operation failed: id=%s, message: %s',
- event.operation_id, event.error_message)
-
-
-def appengine_post(url, data, headers):
- result = urlfetch.fetch(
- method='POST',
- url=url,
- payload=data,
- headers=headers,
- deadline=10)
- return result.status_code, result.content
-
-
-class RobotVerifyTokenHandler(webapp.RequestHandler):
- """Handler for the token_verify request."""
-
- def __init__(self, robot):
- """Initializes self with a specific robot."""
- self._robot = robot
-
- def get(self):
- """Handles the get event for debugging. Ops usually too long."""
- token, st = self._robot.get_verification_token_info()
- logging.info('token=' + token)
- if token is None:
- self.error(404)
- self.response.out.write('No token set')
- return
- if st is not None:
- if self.request.get('st') != st:
- self.response.out.write('Invalid st value passed')
- return
- self.response.out.write(token)
-
-
-def create_robot_webapp(robot, debug=False, extra_handlers=None):
- """Returns an instance of webapp.WSGIApplication with robot handlers."""
- if not extra_handlers:
- extra_handlers = []
- return webapp.WSGIApplication([('.*/_wave/capabilities.xml',
- lambda: CapabilitiesHandler(
- robot.capabilities_xml,
- 'application/xml')),
- ('.*/_wave/robot/profile',
- lambda: ProfileHandler(
- robot.profile_json,
- 'application/json')),
- ('.*/_wave/robot/jsonrpc',
- lambda: RobotEventHandler(robot)),
- ('.*/_wave/verify_token',
- lambda: RobotVerifyTokenHandler(robot)),
- ] + extra_handlers,
- debug=debug)
-
-
-def run(robot, debug=False, log_errors=True, extra_handlers=None):
- """Sets up the webapp handlers for this robot and starts listening.
-
- A robot is typically setup in the following steps:
- 1. Instantiate and define robot.
- 2. Register various handlers that it is interested in.
- 3. Call Run, which will setup the handlers for the app.
- For example:
- robot = Robot('Terminator',
- image_url='http://www.sky.net/models/t800.png',
- profile_url='http://www.sky.net/models/t800.html')
- robot.register_handler(WAVELET_PARTICIPANTS_CHANGED, KillParticipant)
- run(robot)
-
- Args:
- robot: the robot to run. This robot is modified to use app engines
- urlfetch for posting http.
- debug: Optional variable that defaults to False and is passed through
- to the webapp application to determine if it should show debug info.
- log_errors: Optional flag that defaults to True and determines whether
- a default handlers to catch errors should be setup that uses the
- app engine logging to log errors.
- extra_handlers: Optional list of tuples that are passed to the webapp
- to install more handlers. For example, passing
- [('/about', AboutHandler),] would install an extra about handler
- for the robot.
- """
- # App Engine expects to construct a class with no arguments, so we
- # pass a lambda that constructs the appropriate handler with
- # arguments from the enclosing scope.
- if log_errors:
- robot.register_handler(events.OperationError, operation_error_handler)
- robot.set_http_post(appengine_post)
- app = create_robot_webapp(robot, debug, extra_handlers)
- run_wsgi_app(app)
diff --git a/wave/src/main/java/python/api/blip.py b/wave/src/main/java/python/api/blip.py
deleted file mode 100644
index 7f103d9..0000000
--- a/wave/src/main/java/python/api/blip.py
+++ /dev/null
@@ -1,949 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import UserDict
-
-import element
-import errors
-
-import util
-
-class Annotation(object):
- """Models an annotation on a document.
-
- Annotations are key/value pairs over a range of content. Annotations
- can be used to store data or to be interpreted by a client when displaying
- the data.
- """
-
- # Use the following constants to control the display of the client
-
- #: Reserved annotation for setting background color of text.
- BACKGROUND_COLOR = "style/backgroundColor"
- #: Reserved annotation for setting color of text.
- COLOR = "style/color"
- #: Reserved annotation for setting font family of text.
- FONT_FAMILY = "style/fontFamily"
- #: Reserved annotation for setting font family of text.
- FONT_SIZE = "style/fontSize"
- #: Reserved annotation for setting font style of text.
- FONT_STYLE = "style/fontStyle"
- #: Reserved annotation for setting font weight of text.
- FONT_WEIGHT = "style/fontWeight"
- #: Reserved annotation for setting text decoration.
- TEXT_DECORATION = "style/textDecoration"
- #: Reserved annotation for setting vertical alignment.
- VERTICAL_ALIGN = "style/verticalAlign"
- #: Reserved annotation for setting link.
- LINK = "link/manual"
-
- def __init__(self, name, value, start, end):
- self._name = name
- self._value = value
- self._start = start
- self._end = end
-
- @property
- def name(self):
- return self._name
-
- @property
- def value(self):
- return self._value
-
- @property
- def start(self):
- return self._start
-
- @property
- def end(self):
- return self._end
-
- def _shift(self, where, inc):
- """Shift annotation by 'inc' if it (partly) overlaps with 'where'."""
- if self._start >= where:
- self._start += inc
- if self._end >= where:
- self._end += inc
-
- def serialize(self):
- """Serializes the annotation.
-
- Returns:
- A dict containing the name, value, and range values.
- """
- return {'name': self._name,
- 'value': self._value,
- 'range': {'start': self._start,
- 'end': self._end}}
-
-
-class Annotations(object, UserDict.DictMixin):
- """A dictionary-like object containing the annotations, keyed by name."""
-
- def __init__(self, operation_queue, blip):
- self._operation_queue = operation_queue
- self._blip = blip
- self._store = {}
-
- def __contains__(self, what):
- if isinstance(what, Annotation):
- what = what.name
- return what in self._store
-
- def _add_internal(self, name, value, start, end):
- """Internal add annotation does not send out operations."""
- if name in self._store:
- # TODO: use bisect to make this more efficient.
- new_list = []
- for existing in self._store[name]:
- if start > existing.end or end < existing.start:
- new_list.append(existing)
- else:
- if existing.value == value:
- # merge the annotations:
- start = min(existing.start, start)
- end = max(existing.end, end)
- else:
- # chop the bits off the existing annotation
- if existing.start < start:
- new_list.append(Annotation(
- existing.name, existing.value, existing.start, start))
- if existing.end > end:
- new_list.append(Annotation(
- existing.name, existing.value, existing.end, end))
- new_list.append(Annotation(name, value, start, end))
- self._store[name] = new_list
- else:
- self._store[name] = [Annotation(name, value, start, end)]
-
- def _delete_internal(self, name, start=0, end=-1):
- """Remove the passed annotaion from the internal representation."""
- if not name in self._store:
- return
- if end < 0:
- end = len(self._blip) + end
-
- new_list = []
- for a in self._store[name]:
- if start > a.end or end < a.start:
- new_list.append(a)
- elif start < a.start and end > a.end:
- continue
- else:
- if a.start < start:
- new_list.append(Annotation(name, a.value, a.start, start))
- if a.end > end:
- new_list.append(Annotation(name, a.value, end, a.end))
- if new_list:
- self._store[name] = new_list
- else:
- del self._store[name]
-
- def _shift(self, where, inc):
- """Shift annotation by 'inc' if it (partly) overlaps with 'where'."""
- for annotations in self._store.values():
- for annotation in annotations:
- annotation._shift(where, inc)
-
- # Merge fragmented annotations that should be contiguous, for example:
- # Annotation('foo', 'bar', 1, 2) and Annotation('foo', 'bar', 2, 3).
- for name, annotations in self._store.items():
- new_list = []
- for i, annotation in enumerate(annotations):
- name = annotation.name
- value = annotation.value
- start = annotation.start
- end = annotation.end
-
- # Find the last end index.
- for j, next_annotation in enumerate(annotations[i + 1:]):
- # Not contiguous, skip.
- if (end < next_annotation.start):
- break
-
- # Contiguous, merge.
- if (end == next_annotation.start and value == next_annotation.value):
- end = next_annotation.end
- del annotations[j]
- new_list.append(Annotation(name, value, start, end))
- self._store[name] = new_list
-
- def __len__(self):
- return len(self._store)
-
- def __getitem__(self, key):
- return self._store[key]
-
- def __iter__(self):
- for l in self._store.values():
- for ann in l:
- yield ann
-
- def names(self):
- """Return the names of the annotations in the store."""
- return self._store.keys()
-
- def serialize(self):
- """Return a list of the serialized annotations."""
- res = []
- for v in self._store.values():
- res += [a.serialize() for a in v]
- return res
-
-
-class Blips(object, UserDict.DictMixin):
- """A dictionary-like object containing the blips, keyed on blip ID."""
-
- def __init__(self, blips):
- self._blips = blips
-
- def __contains__(self, blip_id):
- return blip_id in self._blips
-
- def __getitem__(self, blip_id):
- return self._blips[blip_id]
-
- def __iter__(self):
- return self._blips.__iter__()
-
- def __len__(self):
- return len(self._blips)
-
- def _add(self, ablip):
- self._blips[ablip.blip_id] = ablip
-
- def _remove_with_id(self, blip_id):
- del_blip = self._blips[blip_id]
- if del_blip:
- # Remove the reference to this blip from its parent.
- parent_blip = self._blips[blip_id].parent_blip
- if parent_blip:
- parent_blip._child_blip_ids.remove(blip_id)
- del self._blips[blip_id]
-
- def get(self, blip_id, default_value=None):
- """Retrieves a blip.
-
- Returns:
- A Blip object. If none found for the ID, it returns None,
- or if default_value is specified, it returns that.
- """
- return self._blips.get(blip_id, default_value)
-
- def serialize(self):
- """Serializes the blips.
- Returns:
- A dict of serialized blips.
- """
- res = {}
- for blip_id, item in self._blips.items():
- res[blip_id] = item.serialize()
- return res
-
- def values(self):
- """Return the blips themselves."""
- return self._blips.values()
-
-
-class BlipRefs(object):
- """Represents a set of references to contents in a blip.
-
- For example, a BlipRefs instance can represent the results
- of a search, an explicitly set range, a regular expression,
- or refer to the entire blip. BlipRefs are used to express
- operations on a blip in a consistent way that can easily
- be transfered to the server.
-
- The typical way of creating a BlipRefs object is to use
- selector methods on the Blip object. Developers will not
- usually instantiate a BlipRefs object directly.
- """
-
- DELETE = 'DELETE'
- REPLACE = 'REPLACE'
- INSERT = 'INSERT'
- INSERT_AFTER = 'INSERT_AFTER'
- ANNOTATE = 'ANNOTATE'
- CLEAR_ANNOTATION = 'CLEAR_ANNOTATION'
- UPDATE_ELEMENT = 'UPDATE_ELEMENT'
-
- def __init__(self, blip, maxres=1):
- self._blip = blip
- self._maxres = maxres
-
- @classmethod
- def all(cls, blip, findwhat, maxres=-1, **restrictions):
- """Construct an instance representing the search for text or elements."""
- obj = cls(blip, maxres)
- obj._findwhat = findwhat
- obj._restrictions = restrictions
- obj._hits = lambda: obj._find(findwhat, maxres, **restrictions)
- if findwhat is None:
- # No findWhat, take the entire blip
- obj._params = {}
- else:
- query = {'maxRes': maxres}
- if isinstance(findwhat, basestring):
- query['textMatch'] = findwhat
- else:
- query['elementMatch'] = findwhat.class_type
- query['restrictions'] = restrictions
- obj._params = {'modifyQuery': query}
- return obj
-
- @classmethod
- def range(cls, blip, begin, end):
- """Constructs an instance representing an explicitly set range."""
- obj = cls(blip)
- obj._begin = begin
- obj._end = end
- obj._hits = lambda: [(begin, end)]
- obj._params = {'range': {'start': begin, 'end': end}}
- return obj
-
- def _elem_matches(self, elem, clz, **restrictions):
- if not isinstance(elem, clz):
- return False
- for key, val in restrictions.items():
- if getattr(elem, key) != val:
- return False
- return True
-
- def _find(self, what, maxres=-1, **restrictions):
- """Iterates where 'what' occurs in the associated blip.
-
- What can be either a string or a class reference.
- Examples:
- self._find('hello') will return the first occurence of the word hello
- self._find(element.Gadget, url='http://example.com/gadget.xml')
- will return the first gadget that has as url example.com.
-
- Args:
- what: what to search for. Can be a class or a string. The class
- should be an element from element.py
- maxres: number of results to return at most, or <= 0 for all.
- restrictions: if what specifies a class, further restrictions
- of the found instances.
- Yields:
- Tuples indicating the range of the matches. For a one
- character/element match at position x, (x, x+1) is yielded.
- """
- blip = self._blip
- if what is None:
- yield 0, len(blip)
- raise StopIteration
- if isinstance(what, basestring):
- idx = blip._content.find(what)
- count = 0
- while idx != -1:
- yield idx, idx + len(what)
- count += 1
- if count == maxres:
- raise StopIteration
- idx = blip._content.find(what, idx + len(what))
- else:
- count = 0
- for idx, el in blip._elements.items():
- if self._elem_matches(el, what, **restrictions):
- yield idx, idx + 1
- count += 1
- if count == maxres:
- raise StopIteration
-
- def _execute(self, modify_how, what, bundled_annotations=None):
- """Executes this BlipRefs object.
-
- Args:
- modify_how: What to do. Any of the operation declared at the top.
- what: Depending on the operation. For delete, has to be None.
- For the others it is a singleton, a list or a function returning
- what to do; for ANNOTATE tuples of (key, value), for the others
- either string or elements.
- If what is a function, it takes three parameters, the content of
- the blip, the beginning of the matching range and the end.
- bundled_annotations: Annotations to apply immediately.
- Raises:
- IndexError when trying to access content outside of the blip.
- ValueError when called with the wrong values.
- Returns:
- self for chainability.
- """
- blip = self._blip
-
- if modify_how != BlipRefs.DELETE:
- if not isinstance(what, list):
- what = [what]
- next_index = 0
-
- matched = []
- # updated_elements is used to store the element type of the
- # element to update
- updated_elements = []
-
- # For now, if we find one markup, we'll use it everywhere.
- next = None
- hit_found = False
-
- for start, end in self._hits():
- hit_found = True
- if start < 0:
- start += len(blip)
- if end == 0:
- end += len(blip)
- if end < 0:
- end += len(blip)
- if len(blip) == 0:
- if start != 0 or end != 0:
- raise IndexError('Start and end have to be 0 for empty document')
- elif start < 0 or end < 1 or start >= len(blip) or end > len(blip):
- raise IndexError('Position outside the document')
- if modify_how == BlipRefs.DELETE:
- for i in range(start, end):
- if i in blip._elements:
- del blip._elements[i]
- blip._delete_annotations(start, end)
- blip._shift(end, start - end)
- blip._content = blip._content[:start] + blip._content[end:]
- else:
- if callable(what):
- next = what(blip._content, start, end)
- matched.append(next)
- else:
- next = what[next_index]
- next_index = (next_index + 1) % len(what)
- if isinstance(next, str):
- next = util.force_unicode(next)
- if modify_how == BlipRefs.ANNOTATE:
- key, value = next
- blip.annotations._add_internal(key, value, start, end)
- elif modify_how == BlipRefs.CLEAR_ANNOTATION:
- blip.annotations._delete_internal(next, start, end)
- elif modify_how == BlipRefs.UPDATE_ELEMENT:
- el = blip._elements.get(start)
- if not el:
- raise ValueError('No element found at index %s' % start)
- # the passing around of types this way feels a bit dirty:
- updated_elements.append(element.Element.from_json({'type': el.type,
- 'properties': next}))
- for k, b in next.items():
- setattr(el, k, b)
- else:
- if modify_how == BlipRefs.INSERT:
- end = start
- elif modify_how == BlipRefs.INSERT_AFTER:
- start = end
- elif modify_how == BlipRefs.REPLACE:
- pass
- else:
- raise ValueError('Unexpected modify_how: ' + modify_how)
-
- if isinstance(next, element.Element):
- text = ' '
- else:
- text = next
-
- # in the case of a replace, and the replacement text is shorter,
- # delete the delta.
- if start != end and len(text) < end - start:
- blip._delete_annotations(start + len(text), end)
-
- blip._shift(end, len(text) + start - end)
- blip._content = blip._content[:start] + text + blip._content[end:]
- if bundled_annotations:
- end_annotation = start + len(text)
- blip._delete_annotations(start, end_annotation)
- for key, value in bundled_annotations:
- blip.annotations._add_internal(key, value, start, end_annotation)
-
- if isinstance(next, element.Element):
- blip._elements[start] = next
-
- # No match found, return immediately without generating op.
- if not hit_found:
- return
-
- operation = blip._operation_queue.document_modify(blip.wave_id,
- blip.wavelet_id,
- blip.blip_id)
- for param, value in self._params.items():
- operation.set_param(param, value)
-
- modify_action = {'modifyHow': modify_how}
- if modify_how == BlipRefs.DELETE:
- pass
- elif modify_how == BlipRefs.UPDATE_ELEMENT:
- modify_action['elements'] = updated_elements
- elif (modify_how == BlipRefs.REPLACE or
- modify_how == BlipRefs.INSERT or
- modify_how == BlipRefs.INSERT_AFTER):
- if callable(what):
- what = matched
- if what:
- if not isinstance(next, element.Element):
- modify_action['values'] = [util.force_unicode(value) for value in what]
- else:
- modify_action['elements'] = what
- elif modify_how == BlipRefs.ANNOTATE:
- modify_action['values'] = [x[1] for x in what]
- modify_action['annotationKey'] = what[0][0]
- elif modify_how == BlipRefs.CLEAR_ANNOTATION:
- modify_action['annotationKey'] = what[0]
- if bundled_annotations:
- modify_action['bundledAnnotations'] = [
- {'key': key, 'value': value} for key, value in bundled_annotations]
- operation.set_param('modifyAction', modify_action)
-
- return self
-
- def insert(self, what, bundled_annotations=None):
- """Inserts what at the matched positions."""
- return self._execute(
- BlipRefs.INSERT, what, bundled_annotations=bundled_annotations)
-
- def insert_after(self, what, bundled_annotations=None):
- """Inserts what just after the matched positions."""
- return self._execute(
- BlipRefs.INSERT_AFTER, what, bundled_annotations=bundled_annotations)
-
- def replace(self, what, bundled_annotations=None):
- """Replaces the matched positions with what."""
- return self._execute(
- BlipRefs.REPLACE, what, bundled_annotations=bundled_annotations)
-
- def delete(self):
- """Deletes the content at the matched positions."""
- return self._execute(BlipRefs.DELETE, None)
-
- def annotate(self, name, value=None):
- """Annotates the content at the matched positions.
-
- You can either specify both name and value to set the
- same annotation, or supply as the first parameter something
- that yields name/value pairs. The name and value should both be strings.
- """
- if value is None:
- what = name
- else:
- what = (name, value)
- return self._execute(BlipRefs.ANNOTATE, what)
-
- def clear_annotation(self, name):
- """Clears the annotation at the matched positions."""
- return self._execute(BlipRefs.CLEAR_ANNOTATION, name)
-
- def update_element(self, new_values):
- """Update an existing element with a set of new values.
-
- For example, this code would update a button value:
- button.update_element({'value': 'Yes'})
- This code would update the 'seen' key in a gadget's state:
- gadget.update_element({'seen': 'yes'})
-
- Args:
- new_values: A dictionary of property names and values.
- """
- return self._execute(BlipRefs.UPDATE_ELEMENT, new_values)
-
- def __nonzero__(self):
- """Return whether we have a value."""
- for start, end in self._hits():
- return True
- return False
-
- def value(self):
- """Convenience method to convert a BlipRefs to value of its first match."""
- for start, end in self._hits():
- if end - start == 1 and start in self._blip._elements:
- return self._blip._elements[start]
- else:
- return self._blip.text[start:end]
- raise ValueError('BlipRefs has no values')
-
- def __getattr__(self, attribute):
- """Mirror the getattr of value().
-
- This allows for clever things like
- first(IMAGE).url
-
- or
-
- blip.annotate_with(key, value).upper()
- """
- return getattr(self.value(), attribute)
-
- def __radd__(self, other):
- """Make it possible to add this to a string."""
- return other + self.value()
-
- def __cmp__(self, other):
- """Support comparision with target."""
- return cmp(self.value(), other)
-
- def __iter__(self):
- for start_end in self._hits():
- yield start_end
-
-
-class Blip(object):
- """Models a single blip instance.
-
- Blips are essentially the documents that make up a conversation. Blips can
- live in a hierarchy of blips. A root blip has no parent blip id, but all
- blips have the ids of the wave and wavelet that they are associated with.
-
- Blips also contain annotations, content and elements, which are accessed via
- the Document object.
- """
-
- def __init__(self, json, other_blips, operation_queue, thread=None,
- reply_threads=None):
- """Inits this blip with JSON data.
-
- Args:
- json: JSON data dictionary from Wave server.
- other_blips: A dictionary like object that can be used to resolve
- ids of blips to blips.
- thread: The BlipThread object that this blip belongs to.
- reply_threads: A list BlipThread objects that are replies to this blip.
- operation_queue: An OperationQueue object to store generated operations
- in.
- """
- self._blip_id = json.get('blipId')
- self._reply_threads = reply_threads or []
- self._thread = thread
- self._operation_queue = operation_queue
- self._child_blip_ids = list(json.get('childBlipIds', []))
- self._content = json.get('content', '')
- self._contributors = set(json.get('contributors', []))
- self._creator = json.get('creator')
- self._last_modified_time = json.get('lastModifiedTime', 0)
- self._version = json.get('version', 0)
- self._parent_blip_id = json.get('parentBlipId')
- self._wave_id = json.get('waveId')
- self._wavelet_id = json.get('waveletId')
- if isinstance(other_blips, Blips):
- self._other_blips = other_blips
- else:
- self._other_blips = Blips(other_blips)
- self._annotations = Annotations(operation_queue, self)
- for annjson in json.get('annotations', []):
- r = annjson['range']
- self._annotations._add_internal(annjson['name'],
- annjson['value'],
- r['start'],
- r['end'])
- self._elements = {}
- json_elements = json.get('elements', {})
- for elem in json_elements:
- self._elements[int(elem)] = element.Element.from_json(json_elements[elem])
- self.raw_data = json
-
- @property
- def blip_id(self):
- """The id of this blip."""
- return self._blip_id
-
- @property
- def wave_id(self):
- """The id of the wave that this blip belongs to."""
- return self._wave_id
-
- @property
- def wavelet_id(self):
- """The id of the wavelet that this blip belongs to."""
- return self._wavelet_id
-
- @property
- def child_blip_ids(self):
- """The list of the ids of this blip's children."""
- return self._child_blip_ids
-
- @property
- def child_blips(self):
- """The list of blips that are children of this blip."""
- return [self._other_blips[blid_id] for blid_id in self._child_blip_ids
- if blid_id in self._other_blips]
-
- @property
- def thread(self):
- """The thread that this blip belongs to."""
- return self._thread
-
- @property
- def reply_threads(self):
- """The list of threads that are replies to this blip."""
- return self._reply_threads
-
- @property
- def inline_reply_threads(self):
- # TODO: Consider moving to constructor
- inline_reply_threads = []
- for reply_thread in self._reply_threads:
- if reply_thread.location > -1:
- inline_reply_threads.append(reply_thread)
- return inline_reply_threads
-
- @property
- def contributors(self):
- """The set of participant ids that contributed to this blip."""
- return self._contributors
-
- @property
- def creator(self):
- """The id of the participant that created this blip."""
- return self._creator
-
- @property
- def last_modified_time(self):
- """The time in seconds since epoch when this blip was last modified."""
- return self._last_modified_time
-
- @property
- def version(self):
- """The version of this blip."""
- return self._version
-
- @property
- def parent_blip_id(self):
- """The parent blip_id or None if this is the root blip."""
- return self._parent_blip_id
-
- @property
- def parent_blip(self):
- """The parent blip or None if it is the root."""
- # if parent_blip_id is None, get will also return None
- return self._other_blips.get(self._parent_blip_id)
-
- @property
- def inline_blip_offset(self):
- """The offset in the parent if this blip is inline or -1 if not.
-
- If the parent is not in the context, this function will always
- return -1 since it can't determine the inline blip status.
- """
- parent = self.parent_blip
- if not parent:
- return -1
- for offset, el in parent._elements.items():
- if el.type == element.Element.INLINE_BLIP_TYPE and el.id == self.blip_id:
- return offset
- return -1
-
- def is_root(self):
- """Returns whether this is the root blip of a wavelet."""
- return self._parent_blip_id is None
-
- @property
- def annotations(self):
- """The annotations for this document."""
- return self._annotations
-
- @property
- def elements(self):
- """Returns a list of elements for this document.
- The elements of a blip are things like forms elements and gadgets
- that cannot be expressed as plain text. In the text of the blip, you'll
- typically find a space as a place holder for the element.
- If you want to retrieve the element at a particular index in the blip, use
- blip[index].value().
- """
- return self._elements.values()
-
- def __len__(self):
- return len(self._content)
-
- def __getitem__(self, item):
- """returns a BlipRefs for the given slice."""
- if isinstance(item, slice):
- if item.step:
- raise errors.Error('Step not supported for blip slices')
- return self.range(item.start, item.stop)
- else:
- return self.at(item)
-
- def __setitem__(self, item, value):
- """short cut for self.range/at().replace(value)."""
- self.__getitem__(item).replace(value)
-
- def __delitem__(self, item):
- """short cut for self.range/at().delete()."""
- self.__getitem__(item).delete()
-
- def _shift(self, where, inc):
- """Move element and annotations after 'where' up by 'inc'."""
- new_elements = {}
- for idx, el in self._elements.items():
- if idx >= where:
- idx += inc
- new_elements[idx] = el
- self._elements = new_elements
- self._annotations._shift(where, inc)
-
- def _delete_annotations(self, start, end):
- """Delete all annotations between 'start' and 'end'."""
- for annotation_name in self._annotations.names():
- self._annotations._delete_internal(annotation_name, start, end)
-
- def all(self, findwhat=None, maxres=-1, **restrictions):
- """Returns a BlipRefs object representing all results for the search.
- If searching for an element, the restrictions can be used to specify
- additional element properties to filter on, like the url of a Gadget.
- """
- return BlipRefs.all(self, findwhat, maxres, **restrictions)
-
- def first(self, findwhat=None, **restrictions):
- """Returns a BlipRefs object representing the first result for the search.
- If searching for an element, the restrictions can be used to specify
- additional element properties to filter on, like the url of a Gadget.
- """
- return BlipRefs.all(self, findwhat, 1, **restrictions)
-
- def at(self, index):
- """Returns a BlipRefs object representing a 1-character range."""
- return BlipRefs.range(self, index, index + 1)
-
- def range(self, start, end):
- """Returns a BlipRefs object representing the range."""
- return BlipRefs.range(self, start, end)
-
- def serialize(self):
- """Return a dictionary representation of this blip ready for json."""
- return {'blipId': self._blip_id,
- 'childBlipIds': list(self._child_blip_ids),
- 'content': self._content,
- 'creator': self._creator,
- 'contributors': list(self._contributors),
- 'lastModifiedTime': self._last_modified_time,
- 'version': self._version,
- 'parentBlipId': self._parent_blip_id,
- 'waveId': self._wave_id,
- 'waveletId': self._wavelet_id,
- 'annotations': self._annotations.serialize(),
- 'elements': dict([(index, e.serialize())
- for index, e in self._elements.items()])
- }
-
- def proxy_for(self, proxy_for_id):
- """Return a view on this blip that will proxy for the specified id.
-
- A shallow copy of the current blip is returned with the proxy_for_id
- set. Any modifications made to this copy will be done using the
- proxy_for_id, i.e. the robot+<proxy_for_id>@appspot.com address will
- be used.
- """
- util.check_is_valid_proxy_for_id(proxy_for_id)
- operation_queue = self._operation_queue.proxy_for(proxy_for_id)
- res = Blip(json={},
- other_blips={},
- operation_queue=operation_queue)
- res._blip_id = self._blip_id
- res._child_blip_ids = self._child_blip_ids
- res._content = self._content
- res._contributors = self._contributors
- res._creator = self._creator
- res._last_modified_time = self._last_modified_time
- res._version = self._version
- res._parent_blip_id = self._parent_blip_id
- res._wave_id = self._wave_id
- res._wavelet_id = self._wavelet_id
- res._other_blips = self._other_blips
- res._annotations = self._annotations
- res._elements = self._elements
- res.raw_data = self.raw_data
- return res
-
- @property
- def text(self):
- """Returns the raw text content of this document."""
- return self._content
-
- def find(self, what, **restrictions):
- """Iterate to matching bits of contents.
-
- Yield either elements or pieces of text.
- """
- br = BlipRefs.all(self, what, **restrictions)
- for start, end in br._hits():
- if end - start == 1 and start in self._elements:
- yield self._elements[start]
- else:
- yield self._content[start:end]
- raise StopIteration
-
- def append(self, what, bundled_annotations=None):
- """Convenience method covering a common pattern."""
- return BlipRefs.all(self, findwhat=None).insert_after(
- what, bundled_annotations=bundled_annotations)
-
- def continue_thread(self):
- """Create and return a blip in the same thread as this blip."""
- blip_data = self._operation_queue.blip_continue_thread(self.wave_id,
- self.wavelet_id,
- self.blip_id)
- new_blip = Blip(blip_data, self._other_blips, self._operation_queue,
- thread=self._thread)
- if self._thread:
- self._thread._add_internal(new_blip)
- self._other_blips._add(new_blip)
- return new_blip
-
- def reply(self):
- """Create and return a reply to this blip."""
- blip_data = self._operation_queue.blip_create_child(self.wave_id,
- self.wavelet_id,
- self.blip_id)
- new_blip = Blip(blip_data, self._other_blips, self._operation_queue)
- self._other_blips._add(new_blip)
- return new_blip
-
- def append_markup(self, markup):
- """Interpret the markup text as xhtml and append the result to the doc.
-
- Args:
- markup: The markup'ed text to append.
- """
- markup = util.force_unicode(markup)
- self._operation_queue.document_append_markup(self.wave_id,
- self.wavelet_id,
- self.blip_id,
- markup)
- self._content += util.parse_markup(markup)
-
- def insert_inline_blip(self, position):
- """Inserts an inline blip into this blip at a specific position.
-
- Args:
- position: Position to insert the blip at. This has to be greater than 0.
-
- Returns:
- The JSON data of the blip that was created.
- """
- if position <= 0:
- raise IndexError(('Illegal inline blip position: %d. Position has to ' +
- 'be greater than 0.') % position)
-
- blip_data = self._operation_queue.document_inline_blip_insert(
- self.wave_id,
- self.wavelet_id,
- self.blip_id,
- position)
- new_blip = Blip(blip_data, self._other_blips, self._operation_queue)
- self._other_blips._add(new_blip)
- return new_blip
diff --git a/wave/src/main/java/python/api/blip_test.py b/wave/src/main/java/python/api/blip_test.py
deleted file mode 100644
index 7cd66e0..0000000
--- a/wave/src/main/java/python/api/blip_test.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the blip module."""
-
-
-import unittest
-
-import blip
-import element
-import ops
-import simplejson
-
-TEST_BLIP_DATA = {
- 'childBlipIds': [],
- 'content': '\nhello world!\nanother line',
- 'contributors': ['robot@test.com', 'user@test.com'],
- 'creator': 'user@test.com',
- 'lastModifiedTime': 1000,
- 'parentBlipId': None,
- 'annotations': [{'range': {'start': 2, 'end': 3},
- 'name': 'key', 'value': 'val'}],
- 'waveId': 'test.com!w+g3h3im',
- 'waveletId': 'test.com!root+conv',
- 'elements':{'14':{'type':'GADGET','properties':{'url':'http://a/b.xml'}}},
-}
-
-CHILD_BLIP_ID = 'b+42'
-ROOT_BLIP_ID = 'b+43'
-
-
-class TestBlip(unittest.TestCase):
- """Tests the primary data structures for the wave model."""
-
- def assertBlipStartswith(self, expected, totest):
- actual = totest.text[:len(expected)]
- self.assertEquals(expected, actual)
-
- def new_blip(self, **args):
- """Create a blip for testing."""
- data = TEST_BLIP_DATA.copy()
- data.update(args)
- res = blip.Blip(data, self.all_blips, self.operation_queue)
- self.all_blips[res.blip_id] = res
- return res
-
- def setUp(self):
- self.all_blips = {}
- self.operation_queue = ops.OperationQueue()
-
- def testBlipProperties(self):
- root = self.new_blip(blipId=ROOT_BLIP_ID,
- childBlipIds=[CHILD_BLIP_ID])
- child = self.new_blip(blipId=CHILD_BLIP_ID,
- parentBlipId=ROOT_BLIP_ID)
- self.assertEquals(ROOT_BLIP_ID, root.blip_id)
- self.assertEquals([CHILD_BLIP_ID], root.child_blip_ids)
- self.assertEquals(set(TEST_BLIP_DATA['contributors']), root.contributors)
- self.assertEquals(TEST_BLIP_DATA['creator'], root.creator)
- self.assertEquals(TEST_BLIP_DATA['content'], root.text)
- self.assertEquals(TEST_BLIP_DATA['lastModifiedTime'],
- root.last_modified_time)
- self.assertEquals(TEST_BLIP_DATA['parentBlipId'], root.parent_blip_id)
- self.assertEquals(TEST_BLIP_DATA['waveId'], root.wave_id)
- self.assertEquals(TEST_BLIP_DATA['waveletId'], root.wavelet_id)
- self.assertEquals(TEST_BLIP_DATA['content'][3], root[3])
- self.assertEquals(element.Gadget.class_type, root[14].type)
- self.assertEquals('http://a/b.xml', root[14].url)
- self.assertEquals('a', root.text[14])
- self.assertEquals(len(TEST_BLIP_DATA['content']), len(root))
- self.assertTrue(root.is_root())
- self.assertFalse(child.is_root())
- self.assertEquals(root, child.parent_blip)
-
- def testBlipSerialize(self):
- root = self.new_blip(blipId=ROOT_BLIP_ID,
- childBlipIds=[CHILD_BLIP_ID])
- serialized = root.serialize()
- unserialized = blip.Blip(serialized, self.all_blips, self.operation_queue)
- self.assertEquals(root.blip_id, unserialized.blip_id)
- self.assertEquals(root.child_blip_ids, unserialized.child_blip_ids)
- self.assertEquals(root.contributors, unserialized.contributors)
- self.assertEquals(root.creator, unserialized.creator)
- self.assertEquals(root.text, unserialized.text)
- self.assertEquals(root.last_modified_time, unserialized.last_modified_time)
- self.assertEquals(root.parent_blip_id, unserialized.parent_blip_id)
- self.assertEquals(root.wave_id, unserialized.wave_id)
- self.assertEquals(root.wavelet_id, unserialized.wavelet_id)
- self.assertTrue(unserialized.is_root())
-
- def testDocumentOperations(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- newlines = [x for x in blip.find('\n')]
- self.assertEquals(2, len(newlines))
- blip.first('world').replace('jupiter')
- bits = blip.text.split('\n')
- self.assertEquals(3, len(bits))
- self.assertEquals('hello jupiter!', bits[1])
- blip.range(2, 5).delete()
- self.assertBlipStartswith('\nho jupiter', blip)
-
- blip.first('ho').insert_after('la')
- self.assertBlipStartswith('\nhola jupiter', blip)
- blip.at(3).insert(' ')
- self.assertBlipStartswith('\nho la jupiter', blip)
-
- def testElementHandling(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- url = 'http://www.test.com/image.png'
-
- org_len = len(blip)
- blip.append(element.Image(url=url))
-
- elems = [elem for elem in blip.find(element.Image, url=url)]
- self.assertEquals(1, len(elems))
- elem = elems[0]
- self.assertTrue(isinstance(elem, element.Image))
- blip.at(1).insert('twelve chars')
- self.assertTrue(blip.text.startswith('\ntwelve charshello'))
-
- elem = blip[org_len + 12].value()
- self.assertTrue(isinstance(elem, element.Image))
-
- blip.first('twelve ').delete()
- self.assertTrue(blip.text.startswith('\nchars'))
-
- elem = blip[org_len + 12 - len('twelve ')].value()
- self.assertTrue(isinstance(elem, element.Image))
-
- blip.first('chars').replace(element.Image(url=url))
- elems = [elem for elem in blip.find(element.Image, url=url)]
- self.assertEquals(2, len(elems))
- self.assertTrue(blip.text.startswith('\n hello'))
- elem = blip[1].value()
- self.assertTrue(isinstance(elem, element.Image))
-
- def testAnnotationHandling(self):
- key = 'style/fontWeight'
-
- def get_bold():
- for an in blip.annotations[key]:
- if an.value == 'bold':
- return an
- return None
-
- json = ('[{"range":{"start":3,"end":6},"name":"%s","value":"bold"}]'
- % key)
- blip = self.new_blip(blipId=ROOT_BLIP_ID,
- annotations=simplejson.loads(json))
- self.assertEquals(1, len(blip.annotations))
- self.assertNotEqual(None, get_bold().value)
- self.assertTrue(key in blip.annotations)
-
- # extend the bold annotation by adding:
- blip.range(5, 8).annotate(key, 'bold')
- self.assertEquals(1, len(blip.annotations))
- self.assertEquals(8, get_bold().end)
-
- # clip by adding a same keyed:
- blip[4:12].annotate(key, 'italic')
- self.assertEquals(2, len(blip.annotations[key]))
- self.assertEquals(4, get_bold().end)
-
- # now split the italic one:
- blip.range(6, 7).clear_annotation(key)
- self.assertEquals(3, len(blip.annotations[key]))
-
- # test names and iteration
- self.assertEquals(1, len(blip.annotations.names()))
- self.assertEquals(3, len([x for x in blip.annotations]))
- blip[3: 5].annotate('foo', 'bar')
- self.assertEquals(2, len(blip.annotations.names()))
- self.assertEquals(4, len([x for x in blip.annotations]))
- blip[3: 5].clear_annotation('foo')
-
- # clear the whole thing
- blip.all().clear_annotation(key)
- # getting to the key should now throw an exception
- self.assertRaises(KeyError, blip.annotations.__getitem__, key)
-
- def testBlipOperations(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- self.assertEquals(1, len(self.all_blips))
-
- otherblip = blip.reply()
- otherblip.append('hello world')
- self.assertEquals('hello world', otherblip.text)
- self.assertEquals(blip.blip_id, otherblip.parent_blip_id)
- self.assertEquals(2, len(self.all_blips))
-
- another = blip.continue_thread()
- another.append('hello world')
- self.assertEquals('hello world', another.text)
- self.assertEquals(blip.parent_blip_id, another.parent_blip_id)
- self.assertEquals(3, len(self.all_blips))
-
- inline = blip.insert_inline_blip(3)
- self.assertEquals(blip.blip_id, inline.parent_blip_id)
- self.assertEquals(4, len(self.all_blips))
-
- def testInsertInlineBlipCantInsertAtTheBeginning(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- self.assertEquals(1, len(self.all_blips))
- self.assertRaises(IndexError, blip.insert_inline_blip, 0)
- self.assertEquals(1, len(self.all_blips))
-
- def testDocumentModify(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- blip.all().replace('a text with text and then some text')
- blip[7].insert('text ')
- blip.all('text').replace('thing')
- self.assertEquals('a thing thing with thing and then some thing',
- blip.text)
-
- def testIteration(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- blip.all().replace('aaa 012 aaa 345 aaa 322')
- count = 0
- prev = -1
- for start, end in blip.all('aaa'):
- count += 1
- self.assertTrue(prev < start)
- prev = start
- self.assertEquals(3, count)
-
-
- def testBlipRefValue(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- content = blip.text
- content = content[:4] + content[5:]
- del blip[4]
- self.assertEquals(content, blip.text)
-
- content = content[:2] + content[3:]
- del blip[2:3]
- self.assertEquals(content, blip.text)
-
- blip[2:3] = 'bike'
- content = content[:2] + 'bike' + content[3:]
- self.assertEquals(content, blip.text)
-
- url = 'http://www.test.com/image.png'
- blip.append(element.Image(url=url))
- self.assertEqual(url, blip.first(element.Image).url)
-
- url2 = 'http://www.test.com/another.png'
- blip[-1].update_element({'url': url2})
- self.assertEqual(url2, blip.first(element.Image).url)
-
- self.assertTrue(blip[3:5] == blip.text[3:5])
-
- blip.append('geheim')
- self.assertTrue(blip.first('geheim'))
- self.assertFalse(blip.first(element.Button))
- blip.append(element.Button(name='test1', value='Click'))
- button = blip.first(element.Button)
- button.update_element({'name': 'test2'})
- self.assertEqual('test2', button.name)
-
- def testReplace(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- blip.all().replace('\nxxxx')
- blip.all('yyy').replace('zzz')
- self.assertEqual('\nxxxx', blip.text)
-
- def testDeleteRangeThatSpansAcrossAnnotationEndPoint(self):
- json = ('[{"range":{"start":1,"end":3},"name":"style","value":"bold"}]')
- blip = self.new_blip(blipId=ROOT_BLIP_ID,
- annotations=simplejson.loads(json),
- content='\nFoo bar.')
- blip.range(2, 4).delete()
- self.assertEqual('\nF bar.', blip.text)
- self.assertEqual(1, blip.annotations['style'][0].start)
- self.assertEqual(2, blip.annotations['style'][0].end)
-
- def testInsertBeforeAnnotationStartPoint(self):
- json = ('[{"range":{"start":4,"end":9},"name":"style","value":"bold"}]')
- blip = self.new_blip(blipId=ROOT_BLIP_ID,
- annotations=simplejson.loads(json),
- content='\nFoo bar.')
- blip.at(4).insert('d and')
- self.assertEqual('\nFood and bar.', blip.text)
- self.assertEqual(9, blip.annotations['style'][0].start)
- self.assertEqual(14, blip.annotations['style'][0].end)
-
- def testDeleteRangeInsideAnnotation(self):
- json = ('[{"range":{"start":1,"end":5},"name":"style","value":"bold"}]')
- blip = self.new_blip(blipId=ROOT_BLIP_ID,
- annotations=simplejson.loads(json),
- content='\nFoo bar.')
- blip.range(2, 4).delete()
-
- self.assertEqual('\nF bar.', blip.text)
- self.assertEqual(1, blip.annotations['style'][0].start)
- self.assertEqual(3, blip.annotations['style'][0].end)
-
- def testReplaceInsideAnnotation(self):
- json = ('[{"range":{"start":1,"end":5},"name":"style","value":"bold"}]')
- blip = self.new_blip(blipId=ROOT_BLIP_ID,
- annotations=simplejson.loads(json),
- content='\nFoo bar.')
- blip.range(2, 4).replace('ooo')
- self.assertEqual('\nFooo bar.', blip.text)
- self.assertEqual(1, blip.annotations['style'][0].start)
- self.assertEqual(6, blip.annotations['style'][0].end)
-
- blip.range(2, 5).replace('o')
- self.assertEqual('\nFo bar.', blip.text)
- self.assertEqual(1, blip.annotations['style'][0].start)
- self.assertEqual(4, blip.annotations['style'][0].end)
-
- def testReplaceSpanAnnotation(self):
- json = ('[{"range":{"start":1,"end":4},"name":"style","value":"bold"}]')
- blip = self.new_blip(blipId=ROOT_BLIP_ID,
- annotations=simplejson.loads(json),
- content='\nFoo bar.')
- blip.range(2, 9).replace('')
- self.assertEqual('\nF', blip.text)
- self.assertEqual(1, blip.annotations['style'][0].start)
- self.assertEqual(2, blip.annotations['style'][0].end)
-
- def testSearchWithNoMatchShouldNotGenerateOperation(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID)
- self.assertEqual(-1, blip.text.find(':('))
- self.assertEqual(0, len(self.operation_queue))
- blip.all(':(').replace(':)')
- self.assertEqual(0, len(self.operation_queue))
-
- def testBlipsRemoveWithId(self):
- blip_dict = {
- ROOT_BLIP_ID: self.new_blip(blipId=ROOT_BLIP_ID,
- childBlipIds=[CHILD_BLIP_ID]),
- CHILD_BLIP_ID: self.new_blip(blipId=CHILD_BLIP_ID,
- parentBlipId=ROOT_BLIP_ID)
- }
- blips = blip.Blips(blip_dict)
- blips._remove_with_id(CHILD_BLIP_ID)
- self.assertEqual(1, len(blips))
- self.assertEqual(0, len(blips[ROOT_BLIP_ID].child_blip_ids))
-
- def testAppendMarkup(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID, content='\nFoo bar.')
- markup = '<p><span>markup<span> content</p>'
- blip.append_markup(markup)
- self.assertEqual(1, len(self.operation_queue))
- self.assertEqual('\nFoo bar.\nmarkup content', blip.text)
-
- def testBundledAnnotations(self):
- blip = self.new_blip(blipId=ROOT_BLIP_ID, content='\nFoo bar.')
- blip.append('not bold')
- blip.append('bold', bundled_annotations=[('style/fontWeight', 'bold')])
- self.assertEqual(2, len(blip.annotations))
- self.assertEqual('bold', blip.annotations['style/fontWeight'][0].value)
-
- def testInlineBlipOffset(self):
- offset = 14
- self.new_blip(blipId=ROOT_BLIP_ID,
- childBlipIds=[CHILD_BLIP_ID],
- elements={str(offset):
- {'type': element.Element.INLINE_BLIP_TYPE,
- 'properties': {'id': CHILD_BLIP_ID}}})
- child = self.new_blip(blipId=CHILD_BLIP_ID,
- parentBlipId=ROOT_BLIP_ID)
- self.assertEqual(offset, child.inline_blip_offset)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/commandline_robot_runner.py b/wave/src/main/java/python/api/commandline_robot_runner.py
deleted file mode 100644
index 9cb3eab..0000000
--- a/wave/src/main/java/python/api/commandline_robot_runner.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Run robot from the commandline for testing.
-
-This robot_runner let's you define event handlers using flags and takes the
-json input from the std in and writes out the json output to stdout.
-
-for example
- cat events | commandline_robot_runner.py \
- --eventdef-blip_submitted="wavelet.title='title'"
-"""
-
-__author__ = 'douwe@google.com (Douwe Osinga)'
-
-import sys
-import urllib
-
-from google3.pyglib import app
-from google3.pyglib import flags
-
-from google3.walkabout.externalagents import api
-
-from google3.walkabout.externalagents.api import blip
-from google3.walkabout.externalagents.api import element
-from google3.walkabout.externalagents.api import errors
-from google3.walkabout.externalagents.api import events
-from google3.walkabout.externalagents.api import ops
-from google3.walkabout.externalagents.api import robot
-from google3.walkabout.externalagents.api import util
-
-FLAGS = flags.FLAGS
-
-for event in events.ALL:
- flags.DEFINE_string('eventdef_' + event.type.lower(),
- '',
- 'Event definition for the %s event' % event.type)
-
-
-def handle_event(src, bot, e, w):
- """Handle an event by executing the source code src."""
- globs = {'e': e, 'w': w, 'api': api, 'bot': bot,
- 'blip': blip, 'element': element, 'errors': errors,
- 'events': events, 'ops': ops, 'robot': robot,
- 'util': util}
- exec src in globs
-
-
-def run_bot(input_file, output_file):
- """Run a robot defined on the command line."""
- cmdbot = robot.Robot('Commandline bot')
- for event in events.ALL:
- src = getattr(FLAGS, 'eventdef_' + event.type.lower())
- src = urllib.unquote_plus(src)
- if src:
- cmdbot.register_handler(event,
- lambda event, wavelet, src=src, bot=cmdbot:
- handle_event(src, bot, event, wavelet))
- json_body = unicode(input_file.read(), 'utf8')
- json_response = cmdbot.process_events(json_body)
- output_file.write(json_response)
-
-
-def main(argv):
- run_bot(sys.stdin, sys.stdout)
-
-if __name__ == '__main__':
- app.run()
diff --git a/wave/src/main/java/python/api/commandline_robot_runner_test.py b/wave/src/main/java/python/api/commandline_robot_runner_test.py
deleted file mode 100644
index 3acbca5..0000000
--- a/wave/src/main/java/python/api/commandline_robot_runner_test.py
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Tests for google3.walkabout.externalagents.api.commandline_robot_runner."""
-
-__author__ = 'douwe@google.com (Douwe Osinga)'
-
-import StringIO
-
-from google3.pyglib import app
-from google3.pyglib import flags
-
-from google3.testing.pybase import googletest
-from google3.walkabout.externalagents.api import commandline_robot_runner
-from google3.walkabout.externalagents.api import events
-
-FLAGS = flags.FLAGS
-
-
-BLIP_JSON = ('{"wdykLROk*13":'
- '{"lastModifiedTime":1242079608457,'
- '"contributors":["someguy@test.com"],'
- '"waveletId":"test.com!conv+root",'
- '"waveId":"test.com!wdykLROk*11",'
- '"parentBlipId":null,'
- '"version":3,'
- '"creator":"someguy@test.com",'
- '"content":"\\nContent!",'
- '"blipId":"wdykLROk*13",'
- '"annotations":[{"range":{"start":0,"end":1},'
- '"name":"user/e/otherguy@test.com","value":"Other"}],'
- '"elements":{},'
- '"childBlipIds":[]}'
- '}')
-
-WAVELET_JSON = ('{"lastModifiedTime":1242079611003,'
- '"title":"A title",'
- '"waveletId":"test.com!conv+root",'
- '"rootBlipId":"wdykLROk*13",'
- '"dataDocuments":null,'
- '"creationTime":1242079608457,'
- '"waveId":"test.com!wdykLROk*11",'
- '"participants":["someguy@test.com","monty@appspot.com"],'
- '"creator":"someguy@test.com",'
- '"rootThread": '
- '{"id":"", "location": "-1", "blipIds": ["wdykLROk*13"]},'
- '"version":5}')
-
-EVENTS_JSON = ('[{"timestamp":1242079611003,'
- '"modifiedBy":"someguy@test.com",'
- '"properties":{"participantsRemoved":[],'
- '"participantsAdded":["monty@appspot.com"]},'
- '"type":"WAVELET_PARTICIPANTS_CHANGED"}]')
-
-TEST_JSON = '{"blips":%s,"wavelet":%s,"events":%s}' % (
- BLIP_JSON, WAVELET_JSON, EVENTS_JSON)
-
-
-class CommandlineRobotRunnerTest(googletest.TestCase):
-
- def testSimpleFlow(self):
- FLAGS.eventdef_wavelet_participants_changed = 'x'
- flag = 'eventdef_' + events.WaveletParticipantsChanged.type.lower()
- setattr(FLAGS, flag, 'w.title="New title!"')
- input_stream = StringIO.StringIO(TEST_JSON)
- output_stream = StringIO.StringIO()
- commandline_robot_runner.run_bot(input_stream, output_stream)
- res = output_stream.getvalue()
- self.assertTrue('wavelet.setTitle' in res)
-
-
-def main(unused_argv):
- googletest.main()
-
-
-if __name__ == '__main__':
- app.run()
diff --git a/wave/src/main/java/python/api/django_oauth.py b/wave/src/main/java/python/api/django_oauth.py
deleted file mode 100644
index 6fd8a3f..0000000
--- a/wave/src/main/java/python/api/django_oauth.py
+++ /dev/null
@@ -1,114 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Filter and decorator to wave enable django sites.
-
-If you want to require wave authentication for every handler, just add
-WaveOAuthMiddleware to your middleware. If you only want to require
-authentication for specific handlers, decorate those with @waveoauth.
-
-In any wave authenticated handler the request object should have a
-waveservice field that can be used to talk to wave.
-
-You can specify the following in your settings:
-WAVE_CONSUMER_KEY: the consumer key passed to the waveservice. defaults to
- anonymous if not set.
-WAVE_CONSUMER_SECRET: the consumer key passed to the waveservice. defaults to
- anonymous if not set.
-WAVE_USE_SANDBOX: whether to use the sandbox for this app. Defaults to false.
-"""
-
-from django.conf import settings
-from django.http import HttpResponse, HttpResponseRedirect
-from django.contrib.auth import authenticate
-
-import base64
-import logging
-from functools import wraps
-
-import waveservice
-
-
-class WaveOAuthMiddleware(object):
- """Wave middleware to authenticate all requests at this site."""
- def process_request(self, request):
- return _oauth_helper(request)
-
-
-def waveoauth(func):
- """Decorator used to specify that a handler requires wave authentication."""
- @wraps(func)
- def inner(request, *args, **kwargs):
- result = _oauth_helper(request)
- if result is not None:
- return result
- return func(request, *args, **kwargs)
- return inner
-
-
-def _oauth_helper(request):
- "Check if we're authenticated and if not, execute the oauth dance."
-
- consumer_key = getattr(settings, 'WAVE_CONSUMER_KEY', 'anonymous')
- consumer_secret = getattr(settings, 'WAVE_CONSUMER_SECRET', 'anonymous')
- use_sandbox = getattr(settings, 'WAVE_USE_SANDBOX', False)
-
- service = waveservice.WaveService(
- consumer_key=consumer_key, consumer_secret=consumer_secret, use_sandbox=use_sandbox)
-
- access_token = request.COOKIES.get('WAVE_ACCESS_TOKEN')
- if access_token:
- service.set_access_token(access_token)
- request.waveservice = service
- return None
-
- # no access token. dance monkey dance.
- oauth_token = request.GET.get('oauth_token')
- verifier = request.GET.get('oauth_verifier')
- request_token = request.COOKIES.get('WAVE_REQUEST_TOKEN')
- meta = request.META
-
- # you'd think there would be something better than this madness:
- this_url = meta.get('HTTP_HOST')
- if not this_url:
- this_url = meta.get('SERVER_NAME')
- port = meta.get('SEVER_PORT')
- if port:
- this_url += ':' + port
- this_url += request.path
- schema = meta.get('wsgi.url_scheme', 'http')
- this_url = schema + '://' + this_url
-
- if not oauth_token or not verifier or not request_token:
- # we're here not returning from a callback. Start.
- request_token = service.fetch_request_token(callback=this_url)
- auth_url = service.generate_authorization_url()
- response = HttpResponseRedirect(auth_url)
- # set a session cookie
- response.set_cookie('WAVE_REQUEST_TOKEN', request_token.to_string())
- return response
- else:
- logging.info('upgrading to access token')
- access_token = service.upgrade_to_access_token(request_token=request_token,
- verifier=verifier)
- # This redirect could be avoided if the caller would set the cookie. This way
- # however we keep the cgi arguments clean.
- response = HttpResponseRedirect(this_url)
- response.set_cookie('WAVE_ACCESS_TOKEN', access_token.to_string(), max_age=24*3600*365)
- return response
diff --git a/wave/src/main/java/python/api/element.py b/wave/src/main/java/python/api/element.py
deleted file mode 100644
index 42e88b6..0000000
--- a/wave/src/main/java/python/api/element.py
+++ /dev/null
@@ -1,370 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Elements are non-text bits living in blips like images, gadgets etc.
-
-This module defines the Element class and the derived classes.
-"""
-
-
-import base64
-import logging
-import sys
-
-import util
-
-
-class Element(object):
- """Elements are non-text content within a document.
-
- These are generally abstracted from the Robot. Although a Robot can query the
- properties of an element it can only interact with the specific types that
- the element represents.
-
- Properties of elements are both accessible directly (image.url) and through
- the get method (image.get('url')). In general, Element
- should not be instantiated by robots, but rather rely on the derived classes.
- """
-
- # INLINE_BLIP_TYPE is not a separate type since it shouldn't be instantiated,
- # only be used for introspection
- INLINE_BLIP_TYPE = "INLINE_BLIP"
-
- def __init__(self, element_type, **properties):
- """Initializes self with the specified type and any properties.
-
- Args:
- element_type: string typed member of ELEMENT_TYPE
- properties: either a dictionary of initial properties, or a dictionary
- with just one member properties that is itself a dictionary of
- properties. This allows us to both use
- e = Element(atype, prop1=val1, prop2=prop2...)
- and
- e = Element(atype, properties={prop1:val1, prop2:prop2..})
- """
- if len(properties) == 1 and 'properties' in properties:
- properties = properties['properties']
- self._type = element_type
- # as long as the operation_queue of an element in None, it is
- # unattached. After an element is acquired by a blip, the blip
- # will set the operation_queue to make sure all changes to the
- # element are properly send to the server.
- self._operation_queue = None
- self._properties = properties.copy()
-
- @property
- def type(self):
- """The type of this element."""
- return self._type
-
- @classmethod
- def from_json(cls, json):
- """Class method to instantiate an Element based on a json string."""
- etype = json['type']
- props = json['properties'].copy()
-
- element_class = ALL.get(etype)
- if not element_class:
- # Unknown type. Server could be newer than we are
- return Element(element_type=etype, properties=props)
-
- return element_class.from_props(props)
-
- def get(self, key, default=None):
- """Standard get interface."""
- return self._properties.get(key, default)
-
- def __getattr__(self, key):
- return self._properties[key]
-
- def serialize(self):
- """Custom serializer for Elements."""
- return util.serialize({'properties': util.non_none_dict(self._properties),
- 'type': self._type})
-
-
-class Input(Element):
- """A single-line input element."""
-
- class_type = 'INPUT'
-
- def __init__(self, name, value=''):
- super(Input, self).__init__(Input.class_type,
- name=name,
- value=value,
- default_value=value)
-
- @classmethod
- def from_props(cls, props):
- return Input(name=props.get('name'), value=props.get('value'))
-
-
-class Check(Element):
- """A checkbox element."""
-
- class_type = 'CHECK'
-
- def __init__(self, name, value=''):
- super(Check, self).__init__(Check.class_type,
- name=name, value=value, default_value=value)
-
- @classmethod
- def from_props(cls, props):
- return Check(name=props.get('name'), value=props.get('value'))
-
-
-class Button(Element):
- """A button element."""
-
- class_type = 'BUTTON'
-
- def __init__(self, name, value):
- super(Button, self).__init__(Button.class_type,
- name=name, value=value)
-
- @classmethod
- def from_props(cls, props):
- return Button(name=props.get('name'), value=props.get('value'))
-
-
-class Label(Element):
- """A label element."""
-
- class_type = 'LABEL'
-
- def __init__(self, label_for, caption):
- super(Label, self).__init__(Label.class_type,
- name=label_for, value=caption)
-
- @classmethod
- def from_props(cls, props):
- return Label(label_for=props.get('name'), caption=props.get('value'))
-
-
-class RadioButton(Element):
- """A radio button element."""
-
- class_type = 'RADIO_BUTTON'
-
- def __init__(self, name, group):
- super(RadioButton, self).__init__(RadioButton.class_type,
- name=name, value=group)
-
- @classmethod
- def from_props(cls, props):
- return RadioButton(name=props.get('name'), group=props.get('value'))
-
-
-class RadioButtonGroup(Element):
- """A group of radio buttons."""
-
- class_type = 'RADIO_BUTTON_GROUP'
-
- def __init__(self, name, value):
- super(RadioButtonGroup, self).__init__(RadioButtonGroup.class_type,
- name=name, value=value)
-
- @classmethod
- def from_props(cls, props):
- return RadioButtonGroup(name=props.get('name'), value=props.get('value'))
-
-
-class Password(Element):
- """A password element."""
-
- class_type = 'PASSWORD'
-
- def __init__(self, name, value):
- super(Password, self).__init__(Password.class_type,
- name=name, value=value)
-
- @classmethod
- def from_props(cls, props):
- return Password(name=props.get('name'), value=props.get('value'))
-
-
-class TextArea(Element):
- """A text area element."""
-
- class_type = 'TEXTAREA'
-
- def __init__(self, name, value):
- super(TextArea, self).__init__(TextArea.class_type,
- name=name, value=value)
-
- @classmethod
- def from_props(cls, props):
- return TextArea(name=props.get('name'), value=props.get('value'))
-
-
-class Line(Element):
- """A line element.
-
- Note that Lines are represented in the text as newlines.
- """
-
- class_type = 'LINE'
-
- # Possible line types:
- #: Designates line as H1, largest heading.
- TYPE_H1 = 'h1'
- #: Designates line as H2 heading.
- TYPE_H2 = 'h2'
- #: Designates line as H3 heading.
- TYPE_H3 = 'h3'
- #: Designates line as H4 heading.
- TYPE_H4 = 'h4'
- #: Designates line as H5, smallest heading.
- TYPE_H5 = 'h5'
- #: Designates line as a bulleted list item.
- TYPE_LI = 'li'
-
- # Possible values for align
- #: Sets line alignment to left.
- ALIGN_LEFT = 'l'
- #: Sets line alignment to right.
- ALIGN_RIGHT = 'r'
- #: Sets line alignment to centered.
- ALIGN_CENTER = 'c'
- #: Sets line alignment to justified.
- ALIGN_JUSTIFIED = 'j'
-
- def __init__(self,
- line_type=None,
- indent=None,
- alignment=None,
- direction=None):
- super(Line, self).__init__(Line.class_type,
- lineType=line_type,
- indent=indent,
- alignment=alignment,
- direction=direction)
-
- @classmethod
- def from_props(cls, props):
- return Line(line_type=props.get('lineType'),
- indent=props.get('indent'),
- alignment=props.get('alignment'),
- direction=props.get('direction'))
-
-
-class Gadget(Element):
- """A gadget element."""
-
- class_type = 'GADGET'
-
- def __init__(self, url, props=None):
- if props is None:
- props = {}
- props['url'] = url
- super(Gadget, self).__init__(Gadget.class_type, properties=props)
-
- @classmethod
- def from_props(cls, props):
- return Gadget(props.get('url'), props)
-
- def serialize(self):
- """Gadgets allow for None values."""
- return {'properties': self._properties, 'type': self._type}
-
- def keys(self):
- """Get the valid keys for this gadget."""
- return [x for x in self._properties.keys() if x != 'url']
-
-
-class Installer(Element):
- """An installer element."""
-
- class_type = 'INSTALLER'
-
- def __init__(self, manifest):
- super(Installer, self).__init__(Installer.class_type, manifest=manifest)
-
- @classmethod
- def from_props(cls, props):
- return Installer(props.get('manifest'))
-
-
-
-class Image(Element):
- """An image element."""
-
- class_type = 'IMAGE'
-
- def __init__(self, url='', width=None, height=None,
- attachmentId=None, caption=None):
- super(Image, self).__init__(Image.class_type, url=url, width=width,
- height=height, attachmentId=attachmentId, caption=caption)
-
- @classmethod
- def from_props(cls, props):
- props = dict([(key.encode('utf-8'), value)
- for key, value in props.items()])
- return apply(Image, [], props)
-
-class Attachment(Element):
- """An attachment element.
-
- To create a new attachment, caption and data are needed.
- mimeType, attachmentId and attachmentUrl are sent via events.
- """
-
- class_type = 'ATTACHMENT'
-
- def __init__(self, caption=None, data=None, mimeType=None, attachmentId=None,
- attachmentUrl=None):
- Attachment.originalData = data
- super(Attachment, self).__init__(Attachment.class_type, caption=caption,
- data=data, mimeType=mimeType, attachmentId=attachmentId,
- attachmentUrl=attachmentUrl)
-
- def __getattr__(self, key):
- if key and key == 'data':
- return Attachment.originalData
- return super(Attachment, self).__getattr__(key)
-
- @classmethod
- def from_props(cls, props):
- props = dict([(key.encode('utf-8'), value)
- for key, value in props.items()])
- return apply(Attachment, [], props)
-
- def serialize(self):
- """Serializes the attachment object into JSON.
-
- The attachment data is base64 encoded.
- """
-
- if self.data:
- self._properties['data'] = base64.encodestring(self.data)
- return super(Attachment, self).serialize()
-
-
-def is_element(cls):
- """Returns whether the passed class is an element."""
- try:
- if not issubclass(cls, Element):
- return False
- h = hasattr(cls, 'class_type')
- return hasattr(cls, 'class_type')
- except TypeError:
- return False
-
-ALL = dict([(item.class_type, item) for item in globals().copy().values()
- if is_element(item)])
diff --git a/wave/src/main/java/python/api/element_test.py b/wave/src/main/java/python/api/element_test.py
deleted file mode 100644
index f1be868..0000000
--- a/wave/src/main/java/python/api/element_test.py
+++ /dev/null
@@ -1,218 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the element module."""
-
-
-import base64
-import unittest
-
-import element
-import util
-
-
-class TestElement(unittest.TestCase):
- """Tests for the element.Element class."""
-
- def testProperties(self):
- el = element.Element(element.Gadget.class_type,
- key='value')
- self.assertEquals('value', el.key)
-
- def testFormElement(self):
- el = element.Input('input')
- self.assertEquals(element.Input.class_type, el.type)
- self.assertEquals(el.value, '')
- self.assertEquals(el.name, 'input')
-
- def testImage(self):
- image = element.Image('http://test.com/image.png', width=100, height=100)
- self.assertEquals(element.Image.class_type, image.type)
- self.assertEquals(image.url, 'http://test.com/image.png')
- self.assertEquals(image.width, 100)
- self.assertEquals(image.height, 100)
-
- def testAttachment(self):
- attachment = element.Attachment(caption='My Favorite', data='SomefakeData')
- self.assertEquals(element.Attachment.class_type, attachment.type)
- self.assertEquals(attachment.caption, 'My Favorite')
- self.assertEquals(attachment.data, 'SomefakeData')
-
- def testGadget(self):
- gadget = element.Gadget('http://test.com/gadget.xml')
- self.assertEquals(element.Gadget.class_type, gadget.type)
- self.assertEquals(gadget.url, 'http://test.com/gadget.xml')
-
- def testInstaller(self):
- installer = element.Installer('http://test.com/installer.xml')
- self.assertEquals(element.Installer.class_type, installer.type)
- self.assertEquals(installer.manifest, 'http://test.com/installer.xml')
-
- def testSerialize(self):
- image = element.Image('http://test.com/image.png', width=100, height=100)
- s = util.serialize(image)
- k = s.keys()
- k.sort()
- # we should really only have three things to serialize
- props = s['properties']
- self.assertEquals(len(props), 3)
- self.assertEquals(props['url'], 'http://test.com/image.png')
- self.assertEquals(props['width'], 100)
- self.assertEquals(props['height'], 100)
-
- def testSerializeAttachment(self):
- attachment = element.Attachment(caption='My Favorite', data='SomefakeData')
- s = util.serialize(attachment)
- k = s.keys()
- k.sort()
- # we should really have two things to serialize
- props = s['properties']
- self.assertEquals(len(props), 2)
- self.assertEquals(props['caption'], 'My Favorite')
- self.assertEquals(props['data'], base64.encodestring('SomefakeData'))
- self.assertEquals(attachment.data, 'SomefakeData')
-
- def testSerializeLine(self):
- line = element.Line(element.Line.TYPE_H1, alignment=element.Line.ALIGN_LEFT)
- s = util.serialize(line)
- k = s.keys()
- k.sort()
- # we should really only have three things to serialize
- props = s['properties']
- self.assertEquals(len(props), 2)
- self.assertEquals(props['alignment'], 'l')
- self.assertEquals(props['lineType'], 'h1')
-
- def testSerializeGadget(self):
- gadget = element.Gadget('http://test.com', {'prop1': 'a', 'prop_cap': None})
- s = util.serialize(gadget)
- k = s.keys()
- k.sort()
- # we should really only have three things to serialize
- props = s['properties']
- self.assertEquals(len(props), 3)
- self.assertEquals(props['url'], 'http://test.com')
- self.assertEquals(props['prop1'], 'a')
- self.assertEquals(props['prop_cap'], None)
-
- def testGadgetElementFromJson(self):
- url = 'http://www.foo.com/gadget.xml'
- json = {
- 'type': element.Gadget.class_type,
- 'properties': {
- 'url': url,
- }
- }
- gadget = element.Element.from_json(json)
- self.assertEquals(element.Gadget.class_type, gadget.type)
- self.assertEquals(url, gadget.url)
-
- def testImageElementFromJson(self):
- url = 'http://www.foo.com/image.png'
- width = '32'
- height = '32'
- attachment_id = '2'
- caption = 'Test Image'
- json = {
- 'type': element.Image.class_type,
- 'properties': {
- 'url': url,
- 'width': width,
- 'height': height,
- 'attachmentId': attachment_id,
- 'caption': caption,
- }
- }
- image = element.Element.from_json(json)
- self.assertEquals(element.Image.class_type, image.type)
- self.assertEquals(url, image.url)
- self.assertEquals(width, image.width)
- self.assertEquals(height, image.height)
- self.assertEquals(attachment_id, image.attachmentId)
- self.assertEquals(caption, image.caption)
-
- def testAttachmentElementFromJson(self):
- caption = 'fake caption'
- data = 'fake data'
- mime_type = 'fake mime'
- attachment_id = 'fake id'
- attachment_url = 'fake URL'
- json = {
- 'type': element.Attachment.class_type,
- 'properties': {
- 'caption': caption,
- 'data': data,
- 'mimeType': mime_type,
- 'attachmentId': attachment_id,
- 'attachmentUrl': attachment_url,
- }
- }
- attachment = element.Element.from_json(json)
- self.assertEquals(element.Attachment.class_type, attachment.type)
- self.assertEquals(caption, attachment.caption)
- self.assertEquals(data, attachment.data)
- self.assertEquals(mime_type, attachment.mimeType)
- self.assertEquals(attachment_id, attachment.attachmentId)
- self.assertEquals(attachment_url, attachment.attachmentUrl)
-
- def testFormElementFromJson(self):
- name = 'button'
- value = 'value'
- default_value = 'foo'
- json = {
- 'type': element.Label.class_type,
- 'properties': {
- 'name': name,
- 'value': value,
- 'defaultValue': default_value,
- }
- }
- el = element.Element.from_json(json)
- self.assertEquals(element.Label.class_type, el.type)
- self.assertEquals(name, el.name)
- self.assertEquals(value, el.value)
-
- def testCanInstantiate(self):
- bag = [element.Check(name='check', value='value'),
- element.Button(name='button', value='caption'),
- element.Input(name='input', value='caption'),
- element.Label(label_for='button', caption='caption'),
- element.RadioButton(name='name', group='group'),
- element.RadioButtonGroup(name='name', value='value'),
- element.Password(name='name', value='geheim'),
- element.TextArea(name='name', value='\n\n\n'),
- element.Installer(manifest='test.com/installer.xml'),
- element.Line(line_type='type',
- indent='3',
- alignment='r',
- direction='d'),
- element.Gadget(url='test.com/gadget.xml',
- props={'key1': 'val1', 'key2': 'val2'}),
- element.Image(url='test.com/image.png', width=100, height=200),
- element.Attachment(caption='fake caption', data='fake data')]
- types_constructed = set([type(x) for x in bag])
- types_required = set(element.ALL.values())
- missing_required = types_constructed.difference(types_required)
- self.assertEquals(missing_required, set())
- missing_constructed = types_required.difference(types_constructed)
- self.assertEquals(missing_constructed, set())
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/errors.py b/wave/src/main/java/python/api/errors.py
deleted file mode 100644
index 89ca0a5..0000000
--- a/wave/src/main/java/python/api/errors.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Contains various API-specific exception classes.
-
-This module contains various specific exception classes that are raised by
-the library back to the client.
-"""
-
-
-class Error(Exception):
- """Base library error type."""
-
-class RpcError(Error):
- """Wave rpc error."""
diff --git a/wave/src/main/java/python/api/events.py b/wave/src/main/java/python/api/events.py
deleted file mode 100644
index d63b16a..0000000
--- a/wave/src/main/java/python/api/events.py
+++ /dev/null
@@ -1,304 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Defines event types that are sent from the wave server.
-
-This module defines all of the event types currently supported by the wave
-server. Each event type is sub classed from Event and has its own
-properties depending on the type.
-"""
-
-
-class Context(object):
- """Specifies constants representing different context requests."""
-
- #: Requests the root blip.
- ROOT = 'ROOT'
- #: Requests the parent blip of the event blip.
- PARENT = 'PARENT'
- #: Requests the siblings blip of the event blip.
- SIBLINGS = 'SIBLINGS'
- #: Requests the child blips of the event blip.
- CHILDREN = 'CHILDREN'
- #: Requests the event blip itself.
- SELF = 'SELF'
- #: Requests all of the blips of the event wavelet.
- ALL = 'ALL'
-
-
-class Event(object):
- """Object describing a single event.
-
- Attributes:
- modified_by: Participant id that caused this event.
-
- timestamp: Timestamp that this event occurred on the server.
-
- type: Type string of this event.
-
- properties: Dictionary of all extra properties. Typically the derrived
- event type should have these explicitly set as attributes, but
- experimental features might appear in properties before that.
-
- blip_id: The blip_id of the blip for blip related events or the root
- blip for wavelet related events.
-
- blip: If available, the blip with id equal to the events blip_id.
-
- proxying_for: If available, the proxyingFor id of the robot that caused the
- event.
- """
-
- def __init__(self, json, wavelet):
- """Inits this event with JSON data.
-
- Args:
- json: JSON data from Wave server.
- """
- self.modified_by = json.get('modifiedBy')
- self.timestamp = json.get('timestamp', 0)
- self.type = json.get('type')
- self.raw_data = json
- self.properties = json.get('properties', {})
- self.blip_id = self.properties.get('blipId')
- self.blip = wavelet.blips.get(self.blip_id)
- self.proxying_for = json.get('proxyingFor')
-
-class WaveletBlipCreated(Event):
- """Event triggered when a new blip is created.
-
- Attributes:
- new_blip_id: The id of the newly created blip.
-
- new_blip: If in context, the actual new blip.
- """
- type = 'WAVELET_BLIP_CREATED'
-
- def __init__(self, json, wavelet):
- super(WaveletBlipCreated, self).__init__(json, wavelet)
- self.new_blip_id = self.properties['newBlipId']
- self.new_blip = wavelet.blips.get(self.new_blip_id)
-
-
-class WaveletBlipRemoved(Event):
- """Event triggered when a new blip is removed.
-
- Attributes:
- removed_blip_id: the id of the removed blip
-
- removed_blip: if in context, the removed blip
- """
- type = 'WAVELET_BLIP_REMOVED'
-
- def __init__(self, json, wavelet):
- super(WaveletBlipRemoved, self).__init__(json, wavelet)
- self.removed_blip_id = self.properties['removedBlipId']
- self.removed_blip = wavelet.blips.get(self.removed_blip_id)
-
-
-class WaveletParticipantsChanged(Event):
- """Event triggered when the participants on a wave change.
-
- Attributes:
- participants_added: List of participants added.
-
- participants_removed: List of participants removed.
- """
- type = 'WAVELET_PARTICIPANTS_CHANGED'
-
- def __init__(self, json, wavelet):
- super(WaveletParticipantsChanged, self).__init__(json, wavelet)
- self.participants_added = self.properties['participantsAdded']
- self.participants_removed = self.properties['participantsRemoved']
-
-
-class WaveletSelfAdded(Event):
- """Event triggered when the robot is added to the wavelet."""
- type = 'WAVELET_SELF_ADDED'
-
-
-class WaveletSelfRemoved(Event):
- """Event triggered when the robot is removed from the wavelet."""
- type = 'WAVELET_SELF_REMOVED'
-
-
-class WaveletTitleChanged(Event):
- """Event triggered when the title of the wavelet has changed.
-
- Attributes:
- title: The new title.
- """
- type = 'WAVELET_TITLE_CHANGED'
-
- def __init__(self, json, wavelet):
- super(WaveletTitleChanged, self).__init__(json, wavelet)
- self.title = self.properties['title']
-
-
-class BlipContributorsChanged(Event):
- """Event triggered when the contributors to this blip change.
-
- Attributes:
- contributors_added: List of contributors that were added.
-
- contributors_removed: List of contributors that were removed.
- """
- type = 'BLIP_CONTRIBUTORS_CHANGED'
-
- def __init__(self, json, wavelet):
- super(BlipContributorsChanged, self).__init__(json, wavelet)
- self.contibutors_added = self.properties['contributorsAdded']
- self.contibutors_removed = self.properties['contributorsRemoved']
-
-
-class BlipSubmitted(Event):
- """Event triggered when a blip is submitted."""
- type = 'BLIP_SUBMITTED'
-
-
-class DocumentChanged(Event):
- """Event triggered when a document is changed.
-
- This event is fired after any changes in the document and should be used
- carefully to keep the amount of traffic to the robot reasonable. Use
- filters where appropriate.
- """
- type = 'DOCUMENT_CHANGED'
-
-
-class FormButtonClicked(Event):
- """Event triggered when a form button is clicked.
-
- Attributes:
- button_name: The name of the button that was clicked.
- """
- type = 'FORM_BUTTON_CLICKED'
-
- def __init__(self, json, wavelet):
- super(FormButtonClicked, self).__init__(json, wavelet)
- self.button_name = self.properties['buttonName']
-
-
-class GadgetStateChanged(Event):
- """Event triggered when the state of a gadget changes.
-
- Attributes:
- index: The index of the gadget that changed in the document.
-
- old_state: The old state of the gadget.
- """
- type = 'GADGET_STATE_CHANGED'
-
- def __init__(self, json, wavelet):
- super(GadgetStateChanged, self).__init__(json, wavelet)
- self.index = self.properties['index']
- self.old_state = self.properties['oldState']
-
-
-class AnnotatedTextChanged(Event):
- """Event triggered when text with an annotation has changed.
-
- This is mainly useful in combination with a filter on the
- name of the annotation.
-
- Attributes:
- name: The name of the annotation.
-
- value: The value of the annotation that changed.
- """
- type = 'ANNOTATED_TEXT_CHANGED'
-
- def __init__(self, json, wavelet):
- super(AnnotatedTextChanged, self).__init__(json, wavelet)
- self.name = self.properties['name']
- self.value = self.properties.get('value')
-
-
-class OperationError(Event):
- """Triggered when an event on the server occurred.
-
- Attributes:
- operation_id: The operation id of the failing operation.
-
- error_message: More information as to what went wrong.
- """
- type = 'OPERATION_ERROR'
-
- def __init__(self, json, wavelet):
- super(OperationError, self).__init__(json, wavelet)
- self.operation_id = self.properties['operationId']
- self.error_message = self.properties['message']
-
-
-class WaveletCreated(Event):
- """Triggered when a new wavelet is created.
-
- This event is only triggered if the robot creates a new
- wavelet and can be used to initialize the newly created wave.
- wavelets created by other participants remain invisible
- to the robot until the robot is added to the wave in
- which case WaveletSelfAdded is triggered.
-
- Attributes:
- message: Whatever string was passed into the new_wave
- call as message (if any).
- """
- type = 'WAVELET_CREATED'
-
- def __init__(self, json, wavelet):
- super(WaveletCreated, self).__init__(json, wavelet)
- self.message = self.properties['message']
-
-
-class WaveletFetched(Event):
- """Triggered when a new wavelet is fetched.
-
- This event is triggered after a robot requests to
- see another wavelet. The robot has to be on the other
- wavelet already.
-
- Attributes:
- message: Whatever string was passed into the new_wave
- call as message (if any).
- """
- type = 'WAVELET_FETCHED'
-
- def __init__(self, json, wavelet):
- super(WaveletFetched, self).__init__(json, wavelet)
- self.message = self.properties['message']
-
-
-class WaveletTagsChanged(Event):
- """Event triggered when the tags on a wavelet change."""
- type = 'WAVELET_TAGS_CHANGED'
-
- def __init__(self, json, wavelet):
- super(WaveletTagsChanged, self).__init__(json, wavelet)
-
-
-def is_event(cls):
- """Returns whether the passed class is an event."""
- try:
- if not issubclass(cls, Event):
- return False
- return hasattr(cls, 'type')
- except TypeError:
- return False
-
-ALL = [item for item in globals().copy().values() if is_event(item)]
diff --git a/wave/src/main/java/python/api/module_test_runner.py b/wave/src/main/java/python/api/module_test_runner.py
deleted file mode 100644
index 409519b..0000000
--- a/wave/src/main/java/python/api/module_test_runner.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Module defines the ModuleTestRunnerClass."""
-
-
-import unittest
-
-
-class ModuleTestRunner(object):
- """Responsible for executing all test cases in a list of modules."""
-
- def __init__(self, module_list=None, module_test_settings=None):
- self.modules = module_list or []
- self.settings = module_test_settings or {}
-
- def RunAllTests(self):
- """Executes all tests present in the list of modules."""
- runner = unittest.TextTestRunner()
- for module in self.modules:
- for setting, value in self.settings.iteritems():
- try:
- setattr(module, setting, value)
- except AttributeError:
- print '\nError running ' + str(setting)
- print '\nRunning all tests in module', module.__name__
- runner.run(unittest.defaultTestLoader.loadTestsFromModule(module))
diff --git a/wave/src/main/java/python/api/oauth/LICENSE b/wave/src/main/java/python/api/oauth/LICENSE
deleted file mode 100644
index 89f0591..0000000
--- a/wave/src/main/java/python/api/oauth/LICENSE
+++ /dev/null
@@ -1,22 +0,0 @@
-The MIT License
-
-Copyright (c) 2007 Andy Smith
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
diff --git a/wave/src/main/java/python/api/oauth/__init__.py b/wave/src/main/java/python/api/oauth/__init__.py
deleted file mode 100644
index d69d952..0000000
--- a/wave/src/main/java/python/api/oauth/__init__.py
+++ /dev/null
@@ -1,541 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-import cgi
-import urllib
-import time
-import random
-import urlparse
-import hmac
-import base64
-
-VERSION = '1.0' # Hi Blaine!
-HTTP_METHOD = 'GET'
-SIGNATURE_METHOD = 'PLAINTEXT'
-
-# Generic exception class
-class OAuthError(RuntimeError):
- def __init__(self, message='OAuth error occured.'):
- self.message = message
-
-# optional WWW-Authenticate header (401 error)
-def build_authenticate_header(realm=''):
- return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
-
-# url escape
-def escape(s):
- # escape '/' too
- return urllib.quote(s, safe='~')
-
-# util function: current timestamp
-# seconds since epoch (UTC)
-def generate_timestamp():
- return int(time.time())
-
-# util function: nonce
-# pseudorandom number
-def generate_nonce(length=8):
- return ''.join(str(random.randint(0, 9)) for i in range(length))
-
-# OAuthConsumer is a data type that represents the identity of the Consumer
-# via its shared secret with the Service Provider.
-class OAuthConsumer(object):
- key = None
- secret = None
-
- def __init__(self, key, secret):
- self.key = key
- self.secret = secret
-
-# OAuthToken is a data type that represents an End User via either an access
-# or request token.
-class OAuthToken(object):
- # access tokens and request tokens
- key = None
- secret = None
-
- '''
- key = the token
- secret = the token secret
- '''
- def __init__(self, key, secret):
- self.key = key
- self.secret = secret
-
- def to_string(self):
- return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
-
- # return a token from something like:
- # oauth_token_secret=digg&oauth_token=digg
- @staticmethod
- def from_string(s):
- params = cgi.parse_qs(s, keep_blank_values=False)
- key = params['oauth_token'][0]
- secret = params['oauth_token_secret'][0]
- return OAuthToken(key, secret)
-
- def __str__(self):
- return self.to_string()
-
-# OAuthRequest represents the request and can be serialized
-class OAuthRequest(object):
- '''
- OAuth parameters:
- - oauth_consumer_key
- - oauth_token
- - oauth_signature_method
- - oauth_signature
- - oauth_timestamp
- - oauth_nonce
- - oauth_version
- ... any additional parameters, as defined by the Service Provider.
- '''
- parameters = None # oauth parameters
- http_method = HTTP_METHOD
- http_url = None
- version = VERSION
-
- def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
- self.http_method = http_method
- self.http_url = http_url
- self.parameters = parameters or {}
-
- def set_parameter(self, parameter, value):
- self.parameters[parameter] = value
-
- def get_parameter(self, parameter):
- try:
- return self.parameters[parameter]
- except:
- raise OAuthError('Parameter not found: %s' % parameter)
-
- def _get_timestamp_nonce(self):
- return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
-
- # get any non-oauth parameters
- def get_nonoauth_parameters(self):
- parameters = {}
- for k, v in self.parameters.iteritems():
- # ignore oauth parameters
- if k.find('oauth_') < 0:
- parameters[k] = v
- return parameters
-
- # serialize as a header for an HTTPAuth request
- def to_header(self, realm=''):
- auth_header = 'OAuth realm="%s"' % realm
- # add the oauth parameters
- if self.parameters:
- for k, v in self.parameters.iteritems():
- auth_header += ', %s="%s"' % (k, escape(str(v)))
- return {'Authorization': auth_header}
-
- # serialize as post data for a POST request
- def to_postdata(self):
- return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems())
-
- # serialize as a url for a GET request
- def to_url(self):
- return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
-
- # return a string that consists of all the parameters that need to be signed
- def get_normalized_parameters(self):
- params = self.parameters
- try:
- # exclude the signature if it exists
- del params['oauth_signature']
- except:
- pass
- key_values = params.items()
- # sort lexicographically, first after key, then after value
- key_values.sort()
- # combine key value pairs in string and escape
- return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values)
-
- # just uppercases the http method
- def get_normalized_http_method(self):
- return self.http_method.upper()
-
- # parses the url and rebuilds it to be scheme://host/path
- def get_normalized_http_url(self):
- parts = urlparse.urlparse(self.http_url)
- url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path
- return url_string
-
- # set the signature parameter to the result of build_signature
- def sign_request(self, signature_method, consumer, token):
- # set the signature method
- self.set_parameter('oauth_signature_method', signature_method.get_name())
- # set the signature
- self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token))
-
- def build_signature(self, signature_method, consumer, token):
- # call the build signature method within the signature method
- return signature_method.build_signature(self, consumer, token)
-
- @staticmethod
- def from_request(http_method, http_url, headers=None, parameters=None, query_string=None):
- # combine multiple parameter sources
- if parameters is None:
- parameters = {}
-
- # headers
- if headers and 'Authorization' in headers:
- auth_header = headers['Authorization']
- # check that the authorization header is OAuth
- if auth_header.index('OAuth') > -1:
- try:
- # get the parameters from the header
- header_params = OAuthRequest._split_header(auth_header)
- parameters.update(header_params)
- except:
- raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
-
- # GET or POST query string
- if query_string:
- query_params = OAuthRequest._split_url_string(query_string)
- parameters.update(query_params)
-
- # URL parameters
- param_str = urlparse.urlparse(http_url)[4] # query
- url_params = OAuthRequest._split_url_string(param_str)
- parameters.update(url_params)
-
- if parameters:
- return OAuthRequest(http_method, http_url, parameters)
-
- return None
-
- @staticmethod
- def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
- if not parameters:
- parameters = {}
-
- defaults = {
- 'oauth_consumer_key': oauth_consumer.key,
- 'oauth_timestamp': generate_timestamp(),
- 'oauth_nonce': generate_nonce(),
- 'oauth_version': OAuthRequest.version,
- }
-
- defaults.update(parameters)
- parameters = defaults
-
- if token:
- parameters['oauth_token'] = token.key
-
- return OAuthRequest(http_method, http_url, parameters)
-
- @staticmethod
- def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
- if not parameters:
- parameters = {}
-
- parameters['oauth_token'] = token.key
-
- if callback:
- parameters['oauth_callback'] = escape(callback)
-
- return OAuthRequest(http_method, http_url, parameters)
-
- # util function: turn Authorization: header into parameters, has to do some unescaping
- @staticmethod
- def _split_header(header):
- params = {}
- parts = header.split(',')
- for param in parts:
- # ignore realm parameter
- if param.find('OAuth realm') > -1:
- continue
- # remove whitespace
- param = param.strip()
- # split key-value
- param_parts = param.split('=', 1)
- # remove quotes and unescape the value
- params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
- return params
-
- # util function: turn url string into parameters, has to do some unescaping
- @staticmethod
- def _split_url_string(param_str):
- parameters = cgi.parse_qs(param_str, keep_blank_values=False)
- for k, v in parameters.iteritems():
- parameters[k] = urllib.unquote(v[0])
- return parameters
-
-# OAuthServer is a worker to check a requests validity against a data store
-class OAuthServer(object):
- timestamp_threshold = 300 # in seconds, five minutes
- version = VERSION
- signature_methods = None
- data_store = None
-
- def __init__(self, data_store=None, signature_methods=None):
- self.data_store = data_store
- self.signature_methods = signature_methods or {}
-
- def set_data_store(self, oauth_data_store):
- self.data_store = data_store
-
- def get_data_store(self):
- return self.data_store
-
- def add_signature_method(self, signature_method):
- self.signature_methods[signature_method.get_name()] = signature_method
- return self.signature_methods
-
- # process a request_token request
- # returns the request token on success
- def fetch_request_token(self, oauth_request):
- try:
- # get the request token for authorization
- token = self._get_token(oauth_request, 'request')
- except OAuthError:
- # no token required for the initial token request
- version = self._get_version(oauth_request)
- consumer = self._get_consumer(oauth_request)
- self._check_signature(oauth_request, consumer, None)
- # fetch a new token
- token = self.data_store.fetch_request_token(consumer)
- return token
-
- # process an access_token request
- # returns the access token on success
- def fetch_access_token(self, oauth_request):
- version = self._get_version(oauth_request)
- consumer = self._get_consumer(oauth_request)
- # get the request token
- token = self._get_token(oauth_request, 'request')
- self._check_signature(oauth_request, consumer, token)
- new_token = self.data_store.fetch_access_token(consumer, token)
- return new_token
-
- # verify an api call, checks all the parameters
- def verify_request(self, oauth_request):
- # -> consumer and token
- version = self._get_version(oauth_request)
- consumer = self._get_consumer(oauth_request)
- # get the access token
- token = self._get_token(oauth_request, 'access')
- self._check_signature(oauth_request, consumer, token)
- parameters = oauth_request.get_nonoauth_parameters()
- return consumer, token, parameters
-
- # authorize a request token
- def authorize_token(self, token, user):
- return self.data_store.authorize_request_token(token, user)
-
- # get the callback url
- def get_callback(self, oauth_request):
- return oauth_request.get_parameter('oauth_callback')
-
- # optional support for the authenticate header
- def build_authenticate_header(self, realm=''):
- return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
-
- # verify the correct version request for this server
- def _get_version(self, oauth_request):
- try:
- version = oauth_request.get_parameter('oauth_version')
- except:
- version = VERSION
- if version and version != self.version:
- raise OAuthError('OAuth version %s not supported.' % str(version))
- return version
-
- # figure out the signature with some defaults
- def _get_signature_method(self, oauth_request):
- try:
- signature_method = oauth_request.get_parameter('oauth_signature_method')
- except:
- signature_method = SIGNATURE_METHOD
- try:
- # get the signature method object
- signature_method = self.signature_methods[signature_method]
- except:
- signature_method_names = ', '.join(self.signature_methods.keys())
- raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
-
- return signature_method
-
- def _get_consumer(self, oauth_request):
- consumer_key = oauth_request.get_parameter('oauth_consumer_key')
- if not consumer_key:
- raise OAuthError('Invalid consumer key.')
- consumer = self.data_store.lookup_consumer(consumer_key)
- if not consumer:
- raise OAuthError('Invalid consumer.')
- return consumer
-
- # try to find the token for the provided request token key
- def _get_token(self, oauth_request, token_type='access'):
- token_field = oauth_request.get_parameter('oauth_token')
- token = self.data_store.lookup_token(token_type, token_field)
- if not token:
- raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
- return token
-
- def _check_signature(self, oauth_request, consumer, token):
- timestamp, nonce = oauth_request._get_timestamp_nonce()
- self._check_timestamp(timestamp)
- self._check_nonce(consumer, token, nonce)
- signature_method = self._get_signature_method(oauth_request)
- try:
- signature = oauth_request.get_parameter('oauth_signature')
- except:
- raise OAuthError('Missing signature.')
- # validate the signature
- valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature)
- if not valid_sig:
- key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
- raise OAuthError('Invalid signature. Expected signature base string: %s' % base)
- built = signature_method.build_signature(oauth_request, consumer, token)
-
- def _check_timestamp(self, timestamp):
- # verify that timestamp is recentish
- timestamp = int(timestamp)
- now = int(time.time())
- lapsed = now - timestamp
- if lapsed > self.timestamp_threshold:
- raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
-
- def _check_nonce(self, consumer, token, nonce):
- # verify that the nonce is uniqueish
- nonce = self.data_store.lookup_nonce(consumer, token, nonce)
- if nonce:
- raise OAuthError('Nonce already used: %s' % str(nonce))
-
-# OAuthClient is a worker to attempt to execute a request
-class OAuthClient(object):
- consumer = None
- token = None
-
- def __init__(self, oauth_consumer, oauth_token):
- self.consumer = oauth_consumer
- self.token = oauth_token
-
- def get_consumer(self):
- return self.consumer
-
- def get_token(self):
- return self.token
-
- def fetch_request_token(self, oauth_request):
- # -> OAuthToken
- raise NotImplementedError
-
- def fetch_access_token(self, oauth_request):
- # -> OAuthToken
- raise NotImplementedError
-
- def access_resource(self, oauth_request):
- # -> some protected resource
- raise NotImplementedError
-
-# OAuthDataStore is a database abstraction used to lookup consumers and tokens
-class OAuthDataStore(object):
-
- def lookup_consumer(self, key):
- # -> OAuthConsumer
- raise NotImplementedError
-
- def lookup_token(self, oauth_consumer, token_type, token_token):
- # -> OAuthToken
- raise NotImplementedError
-
- def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
- # -> OAuthToken
- raise NotImplementedError
-
- def fetch_request_token(self, oauth_consumer):
- # -> OAuthToken
- raise NotImplementedError
-
- def fetch_access_token(self, oauth_consumer, oauth_token):
- # -> OAuthToken
- raise NotImplementedError
-
- def authorize_request_token(self, oauth_token, user):
- # -> OAuthToken
- raise NotImplementedError
-
-# OAuthSignatureMethod is a strategy class that implements a signature method
-class OAuthSignatureMethod(object):
- def get_name(self):
- # -> str
- raise NotImplementedError
-
- def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
- # -> str key, str raw
- raise NotImplementedError
-
- def build_signature(self, oauth_request, oauth_consumer, oauth_token):
- # -> str
- raise NotImplementedError
-
- def check_signature(self, oauth_request, consumer, token, signature):
- built = self.build_signature(oauth_request, consumer, token)
- return built == signature
-
-class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
-
- def get_name(self):
- return 'HMAC-SHA1'
-
- def build_signature_base_string(self, oauth_request, consumer, token):
- sig = (
- escape(oauth_request.get_normalized_http_method()),
- escape(oauth_request.get_normalized_http_url()),
- escape(oauth_request.get_normalized_parameters()),
- )
-
- key = '%s&' % escape(consumer.secret)
- if token:
- key += escape(token.secret)
- raw = '&'.join(sig)
- return key, raw
-
- def build_signature(self, oauth_request, consumer, token):
- # build the base signature string
- key, raw = self.build_signature_base_string(oauth_request, consumer, token)
-
- # hmac object
- try:
- import hashlib # 2.5
- hashed = hmac.new(key, raw, hashlib.sha1)
- except:
- import sha # deprecated
- hashed = hmac.new(key, raw, sha)
-
- # calculate the digest base 64
- return base64.b64encode(hashed.digest())
-
-class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
-
- def get_name(self):
- return 'PLAINTEXT'
-
- def build_signature_base_string(self, oauth_request, consumer, token):
- # concatenate the consumer key and secret
- sig = escape(consumer.secret) + '&'
- if token:
- sig = sig + escape(token.secret)
- return sig
-
- def build_signature(self, oauth_request, consumer, token):
- return self.build_signature_base_string(oauth_request, consumer, token)
diff --git a/wave/src/main/java/python/api/ops.py b/wave/src/main/java/python/api/ops.py
deleted file mode 100644
index 862c4b5..0000000
--- a/wave/src/main/java/python/api/ops.py
+++ /dev/null
@@ -1,482 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Support for operations that can be applied to the server.
-
-Contains classes and utilities for creating operations that are to be
-applied on the server.
-"""
-
-import errors
-import random
-import util
-import sys
-
-
-PROTOCOL_VERSION = '0.22'
-
-# Operation Types
-WAVELET_APPEND_BLIP = 'wavelet.appendBlip'
-WAVELET_SET_TITLE = 'wavelet.setTitle'
-WAVELET_ADD_PARTICIPANT = 'wavelet.participant.add'
-WAVELET_DATADOC_SET = 'wavelet.datadoc.set'
-WAVELET_MODIFY_TAG = 'wavelet.modifyTag'
-WAVELET_MODIFY_PARTICIPANT_ROLE = 'wavelet.modifyParticipantRole'
-BLIP_CONTINUE_THREAD = 'blip.continueThread'
-BLIP_CREATE_CHILD = 'blip.createChild'
-BLIP_DELETE = 'blip.delete'
-DOCUMENT_APPEND_MARKUP = 'document.appendMarkup'
-DOCUMENT_INLINE_BLIP_INSERT = 'document.inlineBlip.insert'
-DOCUMENT_MODIFY = 'document.modify'
-ROBOT_CREATE_WAVELET = 'robot.createWavelet'
-ROBOT_FETCH_WAVE = 'robot.fetchWave'
-ROBOT_NOTIFY = 'robot.notify'
-ROBOT_SEARCH = 'robot.search'
-
-# Assign always NOTIFY_OP_ID to the notify operation so
-# we can easily filter it out later
-NOTIFY_OP_ID = '0'
-
-class Operation(object):
- """Represents a generic operation applied on the server.
-
- This operation class contains data that is filled in depending on the
- operation type.
-
- It can be used directly, but doing so will not result
- in local, transient reflection of state on the blips. In other words,
- creating a 'delete blip' operation will not remove the blip from the local
- context for the duration of this session. It is better to use the OpBased
- model classes directly instead.
- """
-
- def __init__(self, method, opid, params):
- """Initializes this operation with contextual data.
-
- Args:
- method: Method to call or type of operation.
- opid: The id of the operation. Any callbacks will refer to these.
- params: An operation type dependent dictionary
- """
- self.method = method
- self.id = opid
- self.params = params
-
- def __str__(self):
- return '%s[%s]%s' % (self.method, self.id, str(self.params))
-
- def set_param(self, param, value):
- self.params[param] = value
- return self
-
- def serialize(self, method_prefix=''):
- """Serialize the operation.
-
- Args:
- method_prefix: prefixed for each method name to allow for specifying
- a namespace.
-
- Returns:
- a dict representation of the operation.
- """
- if method_prefix and not method_prefix.endswith('.'):
- method_prefix += '.'
- return {'method': method_prefix + self.method,
- 'id': self.id,
- 'params': util.serialize(self.params)}
-
- def set_optional(self, param, value):
- """Sets an optional parameter.
-
- If value is None or "", this is a no op. Otherwise it calls
- set_param.
- """
- if value == '' or value is None:
- return self
- else:
- return self.set_param(param, value)
-
-
-class OperationQueue(object):
- """Wraps the queuing of operations using easily callable functions.
-
- The operation queue wraps single operations as functions and queues the
- resulting operations in-order. Typically there shouldn't be a need to
- call this directly unless operations are needed on entities outside
- of the scope of the robot. For example, to modify a blip that
- does not exist in the current context, you might specify the wave, wavelet
- and blip id to generate an operation.
-
- Any calls to this will not be reflected in the robot in any way.
- For example, calling wavelet_append_blip will not result in a new blip
- being added to the robot, only an operation to be applied on the
- server.
- """
-
- # Some class global counters:
- _next_operation_id = 1
-
- def __init__(self, proxy_for_id=None):
- self.__pending = []
- self._capability_hash = None
- self._proxy_for_id = proxy_for_id
-
- def _new_blipdata(self, wave_id, wavelet_id, initial_content='',
- parent_blip_id=None):
- """Creates JSON of the blip used for this session."""
- temp_blip_id = 'TBD_%s_%s' % (wavelet_id,
- hex(random.randint(0, sys.maxint)))
- return {'waveId': wave_id,
- 'waveletId': wavelet_id,
- 'blipId': temp_blip_id,
- 'content': initial_content,
- 'parentBlipId': parent_blip_id}
-
- def _new_waveletdata(self, domain, participants):
- """Creates an ephemeral WaveletData instance used for this session.
-
- Args:
- domain: the domain to create the data for.
- participants initially on the wavelet
- Returns:
- Blipdata (for the rootblip), WaveletData.
- """
- wave_id = domain + '!TBD_%s' % hex(random.randint(0, sys.maxint))
- wavelet_id = domain + '!conv+root'
- root_blip_data = self._new_blipdata(wave_id, wavelet_id)
- participants = set(participants)
- wavelet_data = {'waveId': wave_id,
- 'waveletId': wavelet_id,
- 'rootBlipId': root_blip_data['blipId'],
- 'participants': participants}
- return root_blip_data, wavelet_data
-
- def __len__(self):
- return len(self.__pending)
-
- def __iter__(self):
- return self.__pending.__iter__()
-
- def clear(self):
- self.__pending = []
-
- def proxy_for(self, proxy):
- """Return a view of this operation queue with the proxying for set to proxy.
-
- This method returns a new instance of an operation queue that shares the
- operation list, but has a different proxying_for_id set so the robot using
- this new queue will send out operations with the proxying_for field set.
- """
- res = OperationQueue()
- res.__pending = self.__pending
- res._capability_hash = self._capability_hash
- res._proxy_for_id = proxy
- return res
-
- def set_capability_hash(self, capability_hash):
- self._capability_hash = capability_hash
-
- def serialize(self, method_prefix=''):
- first = Operation(ROBOT_NOTIFY,
- NOTIFY_OP_ID,
- {'capabilitiesHash': self._capability_hash,
- 'protocolVersion': PROTOCOL_VERSION})
- operations = [first] + self.__pending
- return [op.serialize(method_prefix=method_prefix) for op in operations]
- res = util.serialize(operations)
- return res
-
- def copy_operations(self, other_queue):
- """Copy the pending operations from other_queue into this one."""
- for op in other_queue:
- self.__pending.append(op)
-
- def new_operation(self, method, wave_id, wavelet_id, props=None, **kwprops):
- """Creates and adds a new operation to the operation list."""
- if props is None:
- props = {}
- props.update(kwprops)
- if wave_id is not None:
- props['waveId'] = wave_id
- if wavelet_id is not None:
- props['waveletId'] = wavelet_id
- if self._proxy_for_id:
- props['proxyingFor'] = self._proxy_for_id
- operation = Operation(method,
- 'op%s' % OperationQueue._next_operation_id,
- props)
- self.__pending.append(operation)
- OperationQueue._next_operation_id += 1
- return operation
-
- def wavelet_append_blip(self, wave_id, wavelet_id, initial_content=''):
- """Appends a blip to a wavelet.
-
- Args:
- wave_id: The wave id owning the containing wavelet.
- wavelet_id: The wavelet id that this blip should be appended to.
- initial_content: optionally the content to start with
-
- Returns:
- JSON representing the information of the new blip.
- """
- blip_data = self._new_blipdata(wave_id, wavelet_id, initial_content)
- self.new_operation(WAVELET_APPEND_BLIP, wave_id,
- wavelet_id, blipData=blip_data)
- return blip_data
-
- def wavelet_add_participant(self, wave_id, wavelet_id, participant_id):
- """Adds a participant to a wavelet.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- participant_id: Id of the participant to add.
-
- Returns:
- data for the root_blip, wavelet
- """
- return self.new_operation(WAVELET_ADD_PARTICIPANT, wave_id, wavelet_id,
- participantId=participant_id)
-
- def wavelet_datadoc_set(self, wave_id, wavelet_id, name, data):
- """Sets a key/value pair on the data document of a wavelet.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- name: The key name for this data.
- data: The value of the data to set.
- Returns:
- The operation created.
- """
- return self.new_operation(WAVELET_DATADOC_SET, wave_id, wavelet_id,
- datadocName=name, datadocValue=data)
-
- def robot_create_wavelet(self, domain, participants=None, message=''):
- """Creates a new wavelet.
-
- Args:
- domain: the domain to create the wave in
- participants: initial participants on this wavelet or None if none
- message: an optional payload that is returned with the corresponding
- event.
-
- Returns:
- data for the root_blip, wavelet
- """
- if participants is None:
- participants = []
- blip_data, wavelet_data = self._new_waveletdata(domain, participants)
- op = self.new_operation(ROBOT_CREATE_WAVELET,
- wave_id=wavelet_data['waveId'],
- wavelet_id=wavelet_data['waveletId'],
- waveletData=wavelet_data)
- op.set_optional('message', message)
- return blip_data, wavelet_data
-
- def robot_search(self, query, index=None, num_results=None):
- """Execute a search request.
-
- For now this only makes sense in the data API. Wave does not maintain
- an index for robots so no results will be returned in that scenario.
-
- Args:
- query: what to search for
- index: what index to search from
- num_results: how many results to return
- Returns:
- The operation created.
- """
- op = self.new_operation(
- ROBOT_SEARCH, wave_id=None, wavelet_id=None, query=query)
- if index is not None:
- op.set_param('index', index)
- if num_results is not None:
- op.set_param('numResults', num_results)
- return op
-
- def robot_fetch_wave(self, wave_id, wavelet_id,
- raw_deltas_from_version=-1, return_raw_snapshot=False):
- """Requests a snapshot of the specified wavelet.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- raw_deltas_from_version: If specified, return a raw dump of the
- delta history of this wavelet, starting at the given version.
- This may return only part of the history; use additional
- requests with higher raw_deltas_from_version parameters to
- get the rest.
- return_raw_snapshot: if true, return the raw data for this
- wavelet.
- Returns:
- The operation created.
- """
- op = self.new_operation(ROBOT_FETCH_WAVE, wave_id, wavelet_id)
- if raw_deltas_from_version != -1:
- op.set_param('rawDeltasFromVersion', raw_deltas_from_version)
- if return_raw_snapshot:
- op.set_param('returnRawSnapshot', return_raw_snapshot)
- return op
-
- def wavelet_set_title(self, wave_id, wavelet_id, title):
- """Sets the title of a wavelet.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- title: The title to set.
- Returns:
- The operation created.
- """
- return self.new_operation(WAVELET_SET_TITLE, wave_id, wavelet_id,
- waveletTitle=title)
-
- def wavelet_modify_participant_role(
- self, wave_id, wavelet_id, participant_id, role):
- """Modify the role of a participant on a wavelet.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- participant_id: Id of the participant to add.
- role: the new roles
-
- Returns:
- data for the root_blip, wavelet
- """
- return self.new_operation(WAVELET_MODIFY_PARTICIPANT_ROLE, wave_id,
- wavelet_id, participantId=participant_id,
- participantRole=role)
-
- def wavelet_modify_tag(self, wave_id, wavelet_id, tag, modify_how=None):
- """Modifies a tag in a wavelet.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- tag: The tag (a string).
- modify_how: (optional) how to apply the tag. The default is to add
- the tag. Specify 'remove' to remove. Specify None or 'add' to
- add.
- Returns:
- The operation created.
- """
- return self.new_operation(WAVELET_MODIFY_TAG, wave_id, wavelet_id,
- name=tag).set_optional("modify_how", modify_how)
-
- def blip_create_child(self, wave_id, wavelet_id, blip_id):
- """Creates a child blip of another blip.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- blip_id: The blip id that this operation is applied to.
-
- Returns:
- JSON of blip for which further operations can be applied.
- """
- blip_data = self._new_blipdata(wave_id, wavelet_id, parent_blip_id=blip_id)
- self.new_operation(BLIP_CREATE_CHILD, wave_id, wavelet_id,
- blipId=blip_id,
- blipData=blip_data)
- return blip_data
-
- def blip_continue_thread(self, wave_id, wavelet_id, blip_id):
- """Creates a blip in same thread as specified blip.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- blip_id: The blip id that this operation is applied to.
-
- Returns:
- JSON of blip for which further operations can be applied.
- """
- blip_data = self._new_blipdata(wave_id, wavelet_id)
- self.new_operation(BLIP_CONTINUE_THREAD, wave_id, wavelet_id,
- blipId=blip_id,
- blipData=blip_data)
- return blip_data
-
-
- def blip_delete(self, wave_id, wavelet_id, blip_id):
- """Deletes the specified blip.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- blip_id: The blip id that this operation is applied to.
- Returns:
- The operation created.
- """
- return self.new_operation(BLIP_DELETE, wave_id, wavelet_id, blipId=blip_id)
-
- def document_append_markup(self, wave_id, wavelet_id, blip_id, content):
- """Appends content with markup to a document.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- blip_id: The blip id that this operation is applied to.
- content: The markup content to append.
- Returns:
- The operation created.
- """
- return self.new_operation(DOCUMENT_APPEND_MARKUP, wave_id, wavelet_id,
- blipId=blip_id, content=content)
-
- def document_modify(self, wave_id, wavelet_id, blip_id):
- """Creates and queues a document modify operation
-
- The returned operation still needs to be filled with details before
- it makes sense.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- blip_id: The blip id that this operation is applied to.
- Returns:
- The operation created.
- """
- return self.new_operation(DOCUMENT_MODIFY,
- wave_id,
- wavelet_id,
- blipId=blip_id)
-
- def document_inline_blip_insert(self, wave_id, wavelet_id, blip_id, position):
- """Inserts an inline blip at a specific location.
-
- Args:
- wave_id: The wave id owning that this operation is applied to.
- wavelet_id: The wavelet id that this operation is applied to.
- blip_id: The blip id that this operation is applied to.
- position: The position in the document to insert the blip.
-
- Returns:
- JSON data for the blip that was created for further operations.
- """
- inline_blip_data = self._new_blipdata(wave_id, wavelet_id)
- inline_blip_data['parentBlipId'] = blip_id
- self.new_operation(DOCUMENT_INLINE_BLIP_INSERT, wave_id, wavelet_id,
- blipId=blip_id,
- index=position,
- blipData=inline_blip_data)
- return inline_blip_data
diff --git a/wave/src/main/java/python/api/ops_test.py b/wave/src/main/java/python/api/ops_test.py
deleted file mode 100644
index 37f1d85..0000000
--- a/wave/src/main/java/python/api/ops_test.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the ops module."""
-
-
-import unittest
-
-import ops
-
-
-class TestOperation(unittest.TestCase):
- """Test case for Operation class."""
-
- def testFields(self):
- op = ops.Operation(ops.WAVELET_SET_TITLE, 'opid02',
- {'waveId': 'wavelet-id',
- 'title': 'a title'})
- self.assertEqual(ops.WAVELET_SET_TITLE, op.method)
- self.assertEqual('opid02', op.id)
- self.assertEqual(2, len(op.params))
-
- def testConstructModifyTag(self):
- q = ops.OperationQueue()
- op = q.wavelet_modify_tag('waveid', 'waveletid', 'tag')
- self.assertEqual(3, len(op.params))
- op = q.wavelet_modify_tag(
- 'waveid', 'waveletid', 'tag', modify_how='remove')
- self.assertEqual(4, len(op.params))
-
- def testConstructRobotFetchWave(self):
- q = ops.OperationQueue('proxyid')
- op = q.robot_fetch_wave('wave1', 'wavelet1')
- self.assertEqual(3, len(op.params))
- self.assertEqual('proxyid', op.params['proxyingFor'])
- self.assertEqual('wave1', op.params['waveId'])
- self.assertEqual('wavelet1', op.params['waveletId'])
- op = q.robot_fetch_wave('wave1', 'wavelet1',
- raw_deltas_from_version=5, return_raw_snapshot=True)
- self.assertEqual(5, len(op.params))
- self.assertEqual(5, op.params['rawDeltasFromVersion'])
- self.assertEqual(True, op.params['returnRawSnapshot'])
-
-class TestOperationQueue(unittest.TestCase):
- """Test case for OperationQueue class."""
-
- def testSerialize(self):
- q = ops.OperationQueue()
- q.set_capability_hash('hash')
- op = q.wavelet_modify_tag('waveid', 'waveletid', 'tag')
- json = q.serialize()
- self.assertEqual(2, len(json))
- self.assertEqual('robot.notify', json[0]['method'])
- self.assertEqual('hash', json[0]['params']['capabilitiesHash'])
- self.assertEqual(ops.PROTOCOL_VERSION, json[0]['params']['protocolVersion'])
- self.assertEqual('wavelet.modifyTag', json[1]['method'])
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/robot.py b/wave/src/main/java/python/api/robot.py
deleted file mode 100644
index 99561f6..0000000
--- a/wave/src/main/java/python/api/robot.py
+++ /dev/null
@@ -1,343 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Defines the generic robot classes.
-
-This module provides the Robot class and RobotListener interface,
-as well as some helper functions for web requests and responses.
-"""
-
-import logging
-import sys
-
-try:
- __import__('google3') # setup internal test environment
-except ImportError:
- pass
-
-import simplejson
-import blip
-import errors
-import ops
-import simplejson
-import wavelet
-import waveservice
-
-DEFAULT_PROFILE_URL = (
- 'http://code.google.com/apis/wave/extensions/robots/python-tutorial.html')
-
-
-class Robot(object):
- """Robot metadata class.
-
- This class holds on to basic robot information like the name and profile.
- It also maintains the list of event handlers and cron jobs and
- dispatches events to the appropriate handlers.
- """
-
- def __init__(self, name, image_url='', profile_url=DEFAULT_PROFILE_URL):
- """Initializes self with robot information.
-
- Args:
- name: The name of the robot
- image_url: (optional) url of an image that should be used as the avatar
- for this robot.
- profile_url: (optional) url of a webpage with more information about
- this robot.
- """
- self._handlers = {}
- self._name = name
- self._verification_token = None
- self._st = None
- self._waveservice = waveservice.WaveService()
- self._profile_handler = None
- self._image_url = image_url
- self._profile_url = profile_url
- self._capability_hash = 0
- self._consumer_key = None
- self._http_post = None
-
- @property
- def name(self):
- """Returns the name of the robot."""
- return self._name
-
- @property
- def image_url(self):
- """Returns the URL of the avatar image."""
- return self._image_url
-
- @property
- def profile_url(self):
- """Returns the URL of an info page for the robot."""
- return self._profile_url
-
- def get_verification_token_info(self):
- """Returns the verification token and ST parameter."""
- return self._verification_token, self._st
-
- def get_waveservice(self):
- """Return the currently installed waveservice if available.
-
- Raises:
- Error: if no service is installed.
- """
- if self._waveservice is None:
- raise errors.Error('Oauth has not been setup')
- return self._waveservice
-
- def capabilities_hash(self):
- """Return the capabilities hash as a hex string."""
- return hex(self._capability_hash)
-
- def register_handler(self, event_class, handler, context=None, filter=None):
- """Registers a handler on a specific event type.
-
- Multiple handlers may be registered on a single event type and are
- guaranteed to be called in order of registration.
-
- The handler takes two arguments, the event object and the corresponding
- wavelet.
-
- Args:
- event_class: An event to listen for from the classes defined in the
- events module.
-
- handler: A function handler which takes two arguments, the wavelet for
- the event and the event object.
-
- context: The context to provide for this handler.
-
- filter: Depending on the event, a filter can be specified that restricts
- for which values the event handler will be called from the server.
- Valuable to restrict the amount of traffic send to the robot.
- """
- payload = (handler, event_class, context, filter)
- self._handlers.setdefault(event_class.type, []).append(payload)
- if isinstance(context, list):
- context = ','.join(context)
- self._capability_hash = (self._capability_hash * 13 +
- hash(ops.PROTOCOL_VERSION) +
- hash(event_class.type) +
- hash(context) +
- hash(filter)) & 0xfffffff
-
- def set_verification_token_info(self, token, st=None):
- """Set the verification token used in the ownership verification.
-
- /wave/robot/register starts this process up and will produce this token.
-
- Args:
- token: the token provided by /wave/robot/register.
-
- st: optional parameter to verify the request for the token came from
- the wave server.
- """
- self._verification_token = token
- self._st = st
-
- def set_http_post(self, http_post):
- """Set the http_post handler to use when posting."""
- self._http_post = http_post
- if self._waveservice:
- self._waveservice.set_http_post(http_post)
-
- def setup_oauth(self, consumer_key, consumer_secret,
- server_rpc_base='https://www-opensocial.googleusercontent.com/api/rpc'):
- """Configure this robot to use the oauth'd json rpc.
-
- Args:
- consumer_key: consumer key received from the verification process.
-
- consumer_secret: secret received from the verification process.
-
- server_rpc_base: url of the rpc gateway to use. Specify None for default.
- For wave preview, https://www-opensocial.googleusercontent.com/api/rpc
- should be used.
- For wave sandbox,
- https://www-opensocial-sandbox.googleusercontent.com/api/rpc should be used.
- """
-
- consumer_key_prefix = ''
- # NOTE(ljvderijk): Present for backwards capability.
- if server_rpc_base in [waveservice.WaveService.SANDBOX_RPC_URL,
- waveservice.WaveService.RPC_URL]:
- consumer_key_prefix = 'google.com:'
-
- self._consumer_key = consumer_key_prefix + consumer_key
- self._waveservice = waveservice.WaveService(
- consumer_key=consumer_key,
- consumer_secret=consumer_secret,
- server_rpc_base=server_rpc_base,
- http_post=self._http_post)
-
- def register_profile_handler(self, handler):
- """Sets the profile handler for this robot.
-
- The profile handler will be called when a profile is needed. The handler
- gets passed the name for which a profile is needed or None for the
- robot itself. A dictionary with keys for name, imageUrl and
- profileUrl should be returned.
- """
- self._profile_handler = handler
-
- def capabilities_xml(self):
- """Return this robot's capabilities as an XML string."""
- lines = []
- for capability, payloads in self._handlers.items():
- for payload in payloads:
- handler, event_class, context, filter = payload
- line = ' <w:capability name="%s"' % capability
- if context:
- if isinstance(context, list):
- context = ','.join(context)
- line += ' context="%s"' % context
- if filter:
- line += ' filter="%s"' % filter
- line += '/>\n'
- lines.append(line)
- if self._consumer_key:
- oauth_tag = '<w:consumer_key>%s</w:consumer_key>\n' % self._consumer_key
- else:
- oauth_tag = ''
- return ('<?xml version="1.0"?>\n'
- '<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">\n'
- '<w:version>%s</w:version>\n'
- '%s'
- '<w:protocolversion>%s</w:protocolversion>\n'
- '<w:capabilities>\n'
- '%s'
- '</w:capabilities>\n'
- '</w:robot>\n') % (self.capabilities_hash(),
- oauth_tag,
- ops.PROTOCOL_VERSION,
- '\n'.join(lines))
-
- def profile_json(self, name=None):
- """Returns a JSON representation of the profile.
-
- This method is called both for the basic profile of the robot and to
- get a proxying for profile, in which case name is set. By default
- the information supplied at registration is returned.
-
- Use register_profile_handler to override this default behavior.
- """
- if self._profile_handler:
- data = self._profile_handler(name)
- else:
- data = {'name': self.name,
- 'imageUrl': self.image_url,
- 'profileUrl': self.profile_url}
- return simplejson.dumps(data)
-
- def process_events(self, json):
- """Process an incoming set of events encoded as json."""
- parsed = simplejson.loads(json)
- pending_ops = ops.OperationQueue()
- event_wavelet = self.get_waveservice()._wavelet_from_json(parsed, pending_ops)
-
- for event_data in parsed['events']:
- for payload in self._handlers.get(event_data['type'], []):
- handler, event_class, context, filter = payload
- event = event_class(event_data, event_wavelet)
- handler(event, event_wavelet)
-
- pending_ops.set_capability_hash(self.capabilities_hash())
- return simplejson.dumps(pending_ops.serialize())
-
- def new_wave(self, domain, participants=None, message='', proxy_for_id=None,
- submit=False):
- """Create a new wave with the initial participants on it.
-
- A new wave is returned with its own operation queue. It the
- responsibility of the caller to make sure this wave gets
- submitted to the server, either by calling robot.submit() or
- by calling .submit_with() on the returned wave.
-
- Args:
- domain: the domain to create the wavelet on. This should
- in general correspond to the domain of the incoming
- wavelet. (wavelet.domain). Exceptions are situations
- where the robot is calling new_wave outside of an
- event or when the server is handling multiple domains.
-
- participants: initial participants on the wave. The robot
- as the creator of the wave is always added.
-
- message: a string that will be passed back to the robot
- when the WAVELET_CREATOR event is fired. This is a
- lightweight way to pass around state.
-
- submit: if true, use the active gateway to make a round
- trip to the server. This will return immediately an
- actual waveid/waveletid and blipId for the root blip.
-
- """
- return self.get_waveservice().new_wave(
- domain, participants, message, proxy_for_id, submit)
-
- def fetch_wavelet(self, wave_id, wavelet_id=None, proxy_for_id=None,
- raw_deltas_from_version=-1, return_raw_snapshot=False):
- """Use the REST interface to fetch a wave and return it.
-
- The returned wavelet contains a snapshot of the state of the
- wavelet at that point. It can be used to modify the wavelet,
- but the wavelet might change in between, so treat carefully.
-
- Also note that the wavelet returned has its own operation
- queue. It the responsibility of the caller to make sure this
- wavelet gets submited to the server, either by calling
- robot.submit() or by calling .submit_with() on the returned
- wavelet.
- """
- return self.get_waveservice().fetch_wavelet(
- wave_id, wavelet_id, proxy_for_id, raw_deltas_from_version,
- return_raw_snapshot)
-
- def blind_wavelet(self, json, proxy_for_id=None):
- """Construct a blind wave from a json string.
-
- Call this method if you have a snapshot of a wave that you
- want to operate on outside of an event. Since the wave might
- have changed since you last saw it, you should take care to
- submit operations that are as safe as possible.
-
- Args:
- json: a json object or string containing at least a key
- wavelet defining the wavelet and a key blips defining the
- blips in the view.
-
- proxy_for_id: the proxying information that will be set on the wavelet's
- operation queue.
-
- Returns:
- A new wavelet with its own operation queue. It the
- responsibility of the caller to make sure this wavelet gets
- submited to the server, either by calling robot.submit() or
- by calling .submit_with() on the returned wavelet.
- """
- return self.get_waveservice().blind_wavelet(json, proxy_for_id)
-
- def submit(self, wavelet_to_submit):
- """Submit the pending operations associated with wavelet_to_submit.
-
- Typically the wavelet will be the result of fetch_wavelet, blind_wavelet
- or new_wave.
- """
- return self.get_waveservice().submit(wavelet_to_submit)
diff --git a/wave/src/main/java/python/api/robot_test.py b/wave/src/main/java/python/api/robot_test.py
deleted file mode 100644
index 8d079b1..0000000
--- a/wave/src/main/java/python/api/robot_test.py
+++ /dev/null
@@ -1,257 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the robot module."""
-
-import unittest
-
-import events
-import ops
-import robot
-import simplejson
-
-BLIP_JSON = ('{"wdykLROk*13":'
- '{"lastModifiedTime":1242079608457,'
- '"contributors":["someguy@test.com"],'
- '"waveletId":"test.com!conv+root",'
- '"waveId":"test.com!wdykLROk*11",'
- '"parentBlipId":null,'
- '"version":3,'
- '"creator":"someguy@test.com",'
- '"content":"\\nContent!",'
- '"blipId":"wdykLROk*13","'
- 'annotations":[{"range":{"start":0,"end":1},'
- '"name":"user/e/davidbyttow@google.com","value":"David"}],'
- '"elements":{},'
- '"threadId": "",'
- '"childBlipIds":[]}'
- '}')
-
-WAVELET_JSON = ('{"lastModifiedTime":1242079611003,'
- '"title":"A title",'
- '"waveletId":"test.com!conv+root",'
- '"rootBlipId":"wdykLROk*13",'
- '"dataDocuments":null,'
- '"creationTime":1242079608457,'
- '"waveId":"test.com!wdykLROk*11",'
- '"participants":["someguy@test.com","monty@appspot.com"],'
- '"creator":"someguy@test.com",'
- '"rootThread": '
- '{"id":"", "location": "-1", "blipIds": ["wdykLROk*13"]},'
- '"version":5}')
-
-EVENTS_JSON = ('[{"timestamp":1242079611003,'
- '"modifiedBy":"someguy@test.com",'
- '"properties":{"participantsRemoved":[],'
- '"participantsAdded":["monty@appspot.com"]},'
- '"type":"WAVELET_PARTICIPANTS_CHANGED"}]')
-
-TEST_JSON = '{"blips":%s,"wavelet":%s,"events":%s, "threads": {}}' % (
- BLIP_JSON, WAVELET_JSON, EVENTS_JSON)
-
-NEW_WAVE_JSON = [{"data":
- {"waveletId": "wavesandbox.com!conv+root",
- "blipId": "b+LrODcLZkDlu", "waveId":
- "wavesandbox.com!w+LrODcLZkDlt"},
- "id": "op2"}]
-
-
-class TestRobot(unittest.TestCase):
- """Tests for testing the basic parsing of json in robots."""
-
- def setUp(self):
- self.robot = robot.Robot('Testy')
-
- def testCreateWave(self):
- self.robot.get_waveservice().submit = lambda x: NEW_WAVE_JSON
- new_wave = self.robot.new_wave('wavesandbox.com', submit=True)
- self.assertEqual('wavesandbox.com!w+LrODcLZkDlt', new_wave.wave_id)
-
- def testEventParsing(self):
- def check(event, wavelet):
- # Test some basic properties; the rest should be covered by
- # ops.CreateContext.
- root = wavelet.root_blip
- self.assertEqual(1, len(wavelet.blips))
- self.assertEqual('wdykLROk*13', root.blip_id)
- self.assertEqual('test.com!wdykLROk*11', root.wave_id)
- self.assertEqual('test.com!conv+root', root.wavelet_id)
- self.assertEqual('WAVELET_PARTICIPANTS_CHANGED', event.type)
- self.assertEqual({'participantsRemoved': [],
- 'participantsAdded': ['monty@appspot.com']},
- event.properties)
- self.robot.test_called = True
-
- self.robot.test_called = False
- self.robot.register_handler(events.WaveletParticipantsChanged,
- check)
- json = self.robot.process_events(TEST_JSON)
- self.assertTrue(self.robot.test_called)
- operations = simplejson.loads(json)
- # there should be one operation indicating the current version:
- self.assertEqual(1, len(operations))
-
- def testWrongEventsIgnored(self):
- self.robot.test_called = True
-
- def check(event, wavelet):
- called = True
-
- self.robot.test_called = False
- self.robot.register_handler(events.BlipSubmitted,
- check)
- self.robot.process_events(TEST_JSON)
- self.assertFalse(self.robot.test_called)
-
- def testOperationParsing(self):
- def check(event, wavelet):
- wavelet.reply()
- wavelet.title = 'new title'
- wavelet.root_blip.append_markup('<b>Hello</b>')
-
- self.robot.register_handler(events.WaveletParticipantsChanged,
- check)
- json = self.robot.process_events(TEST_JSON)
- operations = simplejson.loads(json)
- expected = set([ops.ROBOT_NOTIFY,
- ops.WAVELET_APPEND_BLIP,
- ops.WAVELET_SET_TITLE,
- ops.DOCUMENT_APPEND_MARKUP])
- methods = [operation['method'] for operation in operations]
- for method in methods:
- self.assertTrue(method in expected)
- expected.remove(method)
- self.assertEquals(0, len(expected))
-
- def testSerializeWavelets(self):
- wavelet = self.robot.blind_wavelet(TEST_JSON)
- serialized = wavelet.serialize()
- unserialized = self.robot.blind_wavelet(serialized)
- self.assertEquals(wavelet.creator, unserialized.creator)
- self.assertEquals(wavelet.creation_time, unserialized.creation_time)
- self.assertEquals(wavelet.last_modified_time,
- unserialized.last_modified_time)
- self.assertEquals(wavelet.root_blip.blip_id, unserialized.root_blip.blip_id)
- self.assertEquals(wavelet.title, unserialized.title)
- self.assertEquals(wavelet.wave_id, unserialized.wave_id)
- self.assertEquals(wavelet.wavelet_id, unserialized.wavelet_id)
- self.assertEquals(wavelet.domain, unserialized.domain)
-
- def testProxiedBlindWavelet(self):
- def handler(event, wavelet):
- blind_wavelet = self.robot.blind_wavelet(TEST_JSON, 'proxyid')
- blind_wavelet.reply()
- blind_wavelet.submit_with(wavelet)
-
- self.robot.register_handler(events.WaveletParticipantsChanged, handler)
- json = self.robot.process_events(TEST_JSON)
- operations = simplejson.loads(json)
-
- self.assertEqual(2, len(operations))
- self.assertEquals(ops.ROBOT_NOTIFY,
- operations[0]['method'])
- self.assertEquals(ops.WAVELET_APPEND_BLIP, operations[1]['method'])
- self.assertEquals('proxyid', operations[1]['params']['proxyingFor'])
-
- def testCapabilitiesHashIncludesContextAndFilter(self):
- robot1 = robot.Robot('Robot1')
- robot1.register_handler(events.WaveletSelfAdded, lambda: '')
-
- robot2 = robot.Robot('Robot2')
- robot2.register_handler(events.WaveletSelfAdded, lambda: '',
- context=events.Context.ALL)
- self.assertNotEqual(robot1.capabilities_hash(), robot2.capabilities_hash())
-
- robot3 = robot.Robot('Robot3')
- robot2.register_handler(events.WaveletSelfAdded, lambda: '',
- context=events.Context.ALL, filter="foo")
- self.assertNotEqual(robot1.capabilities_hash(), robot2.capabilities_hash())
- self.assertNotEqual(robot1.capabilities_hash(), robot3.capabilities_hash())
- self.assertNotEqual(robot2.capabilities_hash(), robot3.capabilities_hash())
-
-class TestGetCapabilitiesXml(unittest.TestCase):
-
- def setUp(self):
- self.robot = robot.Robot('Testy')
- self.robot.capabilities_hash = lambda: '1'
-
- def assertStringsEqual(self, s1, s2):
- self.assertEqual(s1, s2, 'Strings differ:\n%s--\n%s' % (s1, s2))
-
- def testDefault(self):
- expected = (
- '<?xml version="1.0"?>\n'
- '<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">\n'
- '<w:version>1</w:version>\n'
- '<w:protocolversion>%s</w:protocolversion>\n'
- '<w:capabilities>\n</w:capabilities>\n'
- '</w:robot>\n') % ops.PROTOCOL_VERSION
- xml = self.robot.capabilities_xml()
- self.assertStringsEqual(expected, xml)
-
- def testUrls(self):
- profile_robot = robot.Robot(
- 'Testy',
- image_url='http://example.com/image.png',
- profile_url='http://example.com/profile.xml')
- profile_robot.capabilities_hash = lambda: '1'
- expected = (
- '<?xml version="1.0"?>\n'
- '<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">\n'
- '<w:version>1</w:version>\n'
- '<w:protocolversion>%s</w:protocolversion>\n'
- '<w:capabilities>\n</w:capabilities>\n'
- '</w:robot>\n') % ops.PROTOCOL_VERSION
- xml = profile_robot.capabilities_xml()
- self.assertStringsEqual(expected, xml)
-
- def testConsumerKey(self):
- # setup_oauth doesn't work during testing, so heavy handed setting of
- # properties it is:
- self.robot._consumer_key = 'consumer'
- expected = (
- '<?xml version="1.0"?>\n'
- '<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">\n'
- '<w:version>1</w:version>\n'
- '<w:consumer_key>consumer</w:consumer_key>\n'
- '<w:protocolversion>%s</w:protocolversion>\n'
- '<w:capabilities>\n</w:capabilities>\n'
- '</w:robot>\n') % ops.PROTOCOL_VERSION
- xml = self.robot.capabilities_xml()
- self.assertStringsEqual(expected, xml)
-
- def testCapsAndEvents(self):
- self.robot.register_handler(events.BlipSubmitted, None,
- context=[events.Context.SELF,
- events.Context.ROOT])
- expected = (
- '<?xml version="1.0"?>\n'
- '<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">\n'
- '<w:version>1</w:version>\n'
- '<w:protocolversion>%s</w:protocolversion>\n'
- '<w:capabilities>\n'
- ' <w:capability name="%s" context="SELF,ROOT"/>\n'
- '</w:capabilities>\n'
- '</w:robot>\n') % (ops.PROTOCOL_VERSION, events.BlipSubmitted.type)
- xml = self.robot.capabilities_xml()
- self.assertStringsEqual(expected, xml)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/run_unit_tests.py b/wave/src/main/java/python/api/run_unit_tests.py
deleted file mode 100644
index abb69f4..0000000
--- a/wave/src/main/java/python/api/run_unit_tests.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/python
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Script to run all unit tests in this package."""
-
-
-import blip_test
-import element_test
-import module_test_runner
-import ops_test
-import robot_test
-import util_test
-import wavelet_test
-import search_test
-
-
-def RunUnitTests():
- """Runs all registered unit tests."""
- test_runner = module_test_runner.ModuleTestRunner()
- test_runner.modules = [
- blip_test,
- element_test,
- ops_test,
- robot_test,
- util_test,
- wavelet_test,
- search_test,
- ]
- test_runner.RunAllTests()
-
-
-if __name__ == "__main__":
- RunUnitTests()
diff --git a/wave/src/main/java/python/api/search.py b/wave/src/main/java/python/api/search.py
deleted file mode 100644
index 2167114..0000000
--- a/wave/src/main/java/python/api/search.py
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Defines classes that are needed to model a search (results)."""
-
-import errors
-import logging
-import wavelet
-
-class Results(object):
- """Models a set of search results.
-
- Search results are composed of a list of digests, query, and number of
- results.
- """
-
- def __init__(self, json):
- """Inits this results object with JSON data.
-
- Args:
- json: JSON data dictionary from Wave server.
- """
- if 'searchResults' in json:
- json = json['searchResults']
- self._query = json.get('query')
- self._num_results = json.get('numResults')
- self._digests = []
- self._digests = [Digest(digest_data) for digest_data in json['digests']]
-
- @property
- def query(self):
- """Returns the query for this search."""
- return self._query
-
- @property
- def num_results(self):
- """Returns the number of results for this search."""
- return self._num_results
-
- @property
- def digests(self):
- """Returns a list of digests."""
- return self._digests
-
- def __iter__(self):
- """Iterate over the list of digests."""
- return iter(self._digests)
-
- def serialize(self):
- """Return a dict of the search results properties."""
- return {'query': self._query,
- 'numResults': self._num_results,
- 'digests': [digest.serialize() for digest in self._digests]
- }
-
-
-class Digest(object):
- """Models a single digest.
-
- A digest is composed of title, wave ID, snippet, and participants.
- """
-
- def __init__(self, json):
- """Inits this digest with JSON data.
-
- Args:
- json: JSON data dictionary from Wave server.
- """
- self._wave_id = json.get('waveId')
- self._title = json.get('title')
- self._snippet = json.get('snippet')
- self._blip_count = int(json.get('blipCount'))
- self._unread_count = int(json.get('unreadCount'))
- self._last_modified = json.get('lastModified')
- self._participants = wavelet.Participants(json.get('participants', []),
- {},
- self._wave_id,
- '',
- None)
- self._raw_data = json
-
- @property
- def blip_count(self):
- """Returns the number of blips in this wave."""
- return self._blip_count
-
- @property
- def unread_count(self):
- """Returns the number of unread blips in this wave."""
- return self._unread_count
-
- @property
- def last_modified(self):
- """Returns the last modified date of the wave."""
- return self._last_modified
-
- @property
- def wave_id(self):
- """Returns the digest wave id."""
- return self._wave_id
-
- @property
- def snippet(self):
- """Returns the snippet for the digest."""
- return self._snippet
-
- @property
- def domain(self):
- """Return the domain that the wave belongs to."""
- p = self._wave_id.find('!')
- if p == -1:
- return None
- else:
- return self._wave_id[:p]
-
- @property
- def participants(self):
- """Returns a set of participants on this wave."""
- return self._participants
-
- @property
- def title(self):
- return self._title
-
- def serialize(self):
- """Return a dict of the digest properties."""
- return {'waveId': self._wave_id,
- 'participants': self._participants.serialize(),
- 'title': self._title,
- 'snippet': self._snippet,
- 'blipCount': self._blip_count,
- 'unreadCount': self._unread_count,
- 'lastModified': self._last_modified,
- }
-
- def __str__(self):
- return self._title
diff --git a/wave/src/main/java/python/api/search_test.py b/wave/src/main/java/python/api/search_test.py
deleted file mode 100644
index 9f70beb..0000000
--- a/wave/src/main/java/python/api/search_test.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the search module."""
-
-
-import unittest
-
-import search
-import simplejson
-
-TEST_DIGEST_DATA = {
- 'title': 'Title',
- 'participants': ['pamela.fox@googlewave.com'],
- 'waveId': 'test.com!w+g3h3im',
- 'snippet': 'Best test ever',
- 'blipCount': '10',
- 'unreadCount': '2',
- 'lastModified': '1275658457',
-}
-
-
-TEST_RESULTS_DATA = {
- 'query': 'in:inbox',
- 'numResults': 10,
- 'digests': [
- TEST_DIGEST_DATA,
- TEST_DIGEST_DATA
- ]}
-
-
-class TestResults(unittest.TestCase):
- """Tests the wavelet class."""
-
- def setUp(self):
- self.results = search.Results(TEST_RESULTS_DATA)
-
- def testResultsProperties(self):
- r = self.results
- self.assertEquals(TEST_RESULTS_DATA['query'], r.query)
- self.assertEquals(TEST_RESULTS_DATA['numResults'], r.num_results)
- self.assertEquals(len(TEST_RESULTS_DATA['digests']), len(r.digests))
-
-
-class TestDigest(unittest.TestCase):
- """Tests the wavelet class."""
-
- def setUp(self):
- self.digest = search.Digest(TEST_DIGEST_DATA)
-
- def testDigestProperties(self):
- d = self.digest
- self.assertEquals(TEST_DIGEST_DATA['title'], d.title)
- self.assertEquals(TEST_DIGEST_DATA['waveId'], d.wave_id)
- self.assertEquals(TEST_DIGEST_DATA['snippet'], d.snippet)
- self.assertEquals(TEST_DIGEST_DATA['blipCount'], str(d.blip_count))
- self.assertEquals(TEST_DIGEST_DATA['unreadCount'], str(d.unread_count))
- self.assertEquals(TEST_DIGEST_DATA['lastModified'], d.last_modified)
- self.assertTrue(TEST_DIGEST_DATA['participants'][0] in d.participants)
- self.assertEquals('test.com', d.domain)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/simplejson/LICENSE b/wave/src/main/java/python/api/simplejson/LICENSE
deleted file mode 100644
index 8676302..0000000
--- a/wave/src/main/java/python/api/simplejson/LICENSE
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright (c) 2006 Bob Ippolito
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/wave/src/main/java/python/api/simplejson/__init__.py b/wave/src/main/java/python/api/simplejson/__init__.py
deleted file mode 100644
index a111e0c..0000000
--- a/wave/src/main/java/python/api/simplejson/__init__.py
+++ /dev/null
@@ -1,424 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
-JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
-interchange format.
-
-:mod:`simplejson` exposes an API familiar to users of the standard library
-:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
-version of the :mod:`json` library contained in Python 2.6, but maintains
-compatibility with Python 2.4 and Python 2.5 and (currently) has
-significant performance advantages, even without using the optional C
-extension for speedups.
-
-Encoding basic Python object hierarchies::
-
- >>> import simplejson as json
- >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
- '["foo", {"bar": ["baz", null, 1.0, 2]}]'
- >>> print json.dumps("\"foo\bar")
- "\"foo\bar"
- >>> print json.dumps(u'\u1234')
- "\u1234"
- >>> print json.dumps('\\')
- "\\"
- >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
- {"a": 0, "b": 0, "c": 0}
- >>> from StringIO import StringIO
- >>> io = StringIO()
- >>> json.dump(['streaming API'], io)
- >>> io.getvalue()
- '["streaming API"]'
-
-Compact encoding::
-
- >>> import simplejson as json
- >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
- '[1,2,3,{"4":5,"6":7}]'
-
-Pretty printing::
-
- >>> import simplejson as json
- >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ')
- >>> print '\n'.join([l.rstrip() for l in s.splitlines()])
- {
- "4": 5,
- "6": 7
- }
-
-Decoding JSON::
-
- >>> import simplejson as json
- >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
- >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
- True
- >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
- True
- >>> from StringIO import StringIO
- >>> io = StringIO('["streaming API"]')
- >>> json.load(io)[0] == 'streaming API'
- True
-
-Specializing JSON object decoding::
-
- >>> import simplejson as json
- >>> def as_complex(dct):
- ... if '__complex__' in dct:
- ... return complex(dct['real'], dct['imag'])
- ... return dct
- ...
- >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
- ... object_hook=as_complex)
- (1+2j)
- >>> from decimal import Decimal
- >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
- True
-
-Specializing JSON object encoding::
-
- >>> import simplejson as json
- >>> def encode_complex(obj):
- ... if isinstance(obj, complex):
- ... return [obj.real, obj.imag]
- ... raise TypeError(repr(o) + " is not JSON serializable")
- ...
- >>> json.dumps(2 + 1j, default=encode_complex)
- '[2.0, 1.0]'
- >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
- '[2.0, 1.0]'
- >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
- '[2.0, 1.0]'
-
-
-Using simplejson.tool from the shell to validate and pretty-print::
-
- $ echo '{"json":"obj"}' | python -m simplejson.tool
- {
- "json": "obj"
- }
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
- Expecting property name: line 1 column 2 (char 2)
-"""
-__version__ = '2.1.0'
-__all__ = [
- 'dump', 'dumps', 'load', 'loads',
- 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
- 'OrderedDict',
-]
-
-__author__ = 'Bob Ippolito <bob@redivi.com>'
-
-from decoder import JSONDecoder, JSONDecodeError
-from encoder import JSONEncoder
-try:
- from collections import OrderedDict
-except ImportError:
- from ordered_dict import OrderedDict
-
-_default_encoder = JSONEncoder(
- skipkeys=False,
- ensure_ascii=True,
- check_circular=True,
- allow_nan=True,
- indent=None,
- separators=None,
- encoding='utf-8',
- default=None,
-)
-
-def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, **kw):
- """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
- ``.write()``-supporting file-like object).
-
- If ``skipkeys`` is true then ``dict`` keys that are not basic types
- (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
- will be skipped instead of raising a ``TypeError``.
-
- If ``ensure_ascii`` is false, then the some chunks written to ``fp``
- may be ``unicode`` instances, subject to normal Python ``str`` to
- ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
- understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
- to cause an error.
-
- If ``check_circular`` is false, then the circular reference check
- for container types will be skipped and a circular reference will
- result in an ``OverflowError`` (or worse).
-
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
- serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
- in strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
- If *indent* is a string, then JSON array elements and object members
- will be pretty-printed with a newline followed by that string repeated
- for each level of nesting. ``None`` (the default) selects the most compact
- representation without any newlines. For backwards compatibility with
- versions of simplejson earlier than 2.1.0, an integer is also accepted
- and is converted to a string with that many spaces.
-
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
-
- ``encoding`` is the character encoding for str instances, default is UTF-8.
-
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
-
- To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
- ``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg.
-
- """
- # cached encoder
- if (not skipkeys and ensure_ascii and
- check_circular and allow_nan and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and not kw):
- iterable = _default_encoder.iterencode(obj)
- else:
- if cls is None:
- cls = JSONEncoder
- iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding,
- default=default, **kw).iterencode(obj)
- # could accelerate with writelines in some versions of Python, at
- # a debuggability cost
- for chunk in iterable:
- fp.write(chunk)
-
-
-def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, **kw):
- """Serialize ``obj`` to a JSON formatted ``str``.
-
- If ``skipkeys`` is false then ``dict`` keys that are not basic types
- (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
- will be skipped instead of raising a ``TypeError``.
-
- If ``ensure_ascii`` is false, then the return value will be a
- ``unicode`` instance subject to normal Python ``str`` to ``unicode``
- coercion rules instead of being escaped to an ASCII ``str``.
-
- If ``check_circular`` is false, then the circular reference check
- for container types will be skipped and a circular reference will
- result in an ``OverflowError`` (or worse).
-
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
- serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
- strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
- If ``indent`` is a string, then JSON array elements and object members
- will be pretty-printed with a newline followed by that string repeated
- for each level of nesting. ``None`` (the default) selects the most compact
- representation without any newlines. For backwards compatibility with
- versions of simplejson earlier than 2.1.0, an integer is also accepted
- and is converted to a string with that many spaces.
-
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
-
- ``encoding`` is the character encoding for str instances, default is UTF-8.
-
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
-
- To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
- ``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg.
-
- """
- # cached encoder
- if (not skipkeys and ensure_ascii and
- check_circular and allow_nan and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and not kw):
- return _default_encoder.encode(obj)
- if cls is None:
- cls = JSONEncoder
- return cls(
- skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding, default=default,
- **kw).encode(obj)
-
-
-_default_decoder = JSONDecoder(encoding=None, object_hook=None,
- object_pairs_hook=None)
-
-
-def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
- """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
- a JSON document) to a Python object.
-
- *encoding* determines the encoding used to interpret any
- :class:`str` objects decoded by this instance (``'utf-8'`` by
- default). It has no effect when decoding :class:`unicode` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as :class:`unicode`.
-
- *object_hook*, if specified, will be called with the result of every
- JSON object decoded and its return value will be used in place of the
- given :class:`dict`. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- *object_pairs_hook* is an optional function that will be called with
- the result of any object literal decode with an ordered list of pairs.
- The return value of *object_pairs_hook* will be used instead of the
- :class:`dict`. This feature can be used to implement custom decoders
- that rely on the order that the key and value pairs are decoded (for
- example, :func:`collections.OrderedDict` will remember the order of
- insertion). If *object_hook* is also defined, the *object_pairs_hook*
- takes priority.
-
- *parse_float*, if specified, will be called with the string of every
- JSON float to be decoded. By default, this is equivalent to
- ``float(num_str)``. This can be used to use another datatype or parser
- for JSON floats (e.g. :class:`decimal.Decimal`).
-
- *parse_int*, if specified, will be called with the string of every
- JSON int to be decoded. By default, this is equivalent to
- ``int(num_str)``. This can be used to use another datatype or parser
- for JSON integers (e.g. :class:`float`).
-
- *parse_constant*, if specified, will be called with one of the
- following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
- can be used to raise an exception if invalid JSON numbers are
- encountered.
-
- To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
- kwarg.
-
- """
- return loads(fp.read(),
- encoding=encoding, cls=cls, object_hook=object_hook,
- parse_float=parse_float, parse_int=parse_int,
- parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
- **kw)
-
-
-def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
- """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
- document) to a Python object.
-
- *encoding* determines the encoding used to interpret any
- :class:`str` objects decoded by this instance (``'utf-8'`` by
- default). It has no effect when decoding :class:`unicode` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as :class:`unicode`.
-
- *object_hook*, if specified, will be called with the result of every
- JSON object decoded and its return value will be used in place of the
- given :class:`dict`. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- *object_pairs_hook* is an optional function that will be called with
- the result of any object literal decode with an ordered list of pairs.
- The return value of *object_pairs_hook* will be used instead of the
- :class:`dict`. This feature can be used to implement custom decoders
- that rely on the order that the key and value pairs are decoded (for
- example, :func:`collections.OrderedDict` will remember the order of
- insertion). If *object_hook* is also defined, the *object_pairs_hook*
- takes priority.
-
- *parse_float*, if specified, will be called with the string of every
- JSON float to be decoded. By default, this is equivalent to
- ``float(num_str)``. This can be used to use another datatype or parser
- for JSON floats (e.g. :class:`decimal.Decimal`).
-
- *parse_int*, if specified, will be called with the string of every
- JSON int to be decoded. By default, this is equivalent to
- ``int(num_str)``. This can be used to use another datatype or parser
- for JSON integers (e.g. :class:`float`).
-
- *parse_constant*, if specified, will be called with one of the
- following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
- can be used to raise an exception if invalid JSON numbers are
- encountered.
-
- To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
- kwarg.
-
- """
- if (cls is None and encoding is None and object_hook is None and
- parse_int is None and parse_float is None and
- parse_constant is None and object_pairs_hook is None and not kw):
- return _default_decoder.decode(s)
- if cls is None:
- cls = JSONDecoder
- if object_hook is not None:
- kw['object_hook'] = object_hook
- if object_pairs_hook is not None:
- kw['object_pairs_hook'] = object_pairs_hook
- if parse_float is not None:
- kw['parse_float'] = parse_float
- if parse_int is not None:
- kw['parse_int'] = parse_int
- if parse_constant is not None:
- kw['parse_constant'] = parse_constant
- return cls(encoding=encoding, **kw).decode(s)
-
-
-def _toggle_speedups(enabled):
- import simplejson.decoder as dec
- import simplejson.encoder as enc
- import simplejson.scanner as scan
- try:
- from simplejson._speedups import make_encoder as c_make_encoder
- except ImportError:
- c_make_encoder = None
- if enabled:
- dec.scanstring = dec.c_scanstring or dec.py_scanstring
- enc.c_make_encoder = c_make_encoder
- enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
- enc.py_encode_basestring_ascii)
- scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
- else:
- dec.scanstring = dec.py_scanstring
- enc.c_make_encoder = None
- enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
- scan.make_scanner = scan.py_make_scanner
- dec.make_scanner = scan.make_scanner
- global _default_decoder
- _default_decoder = JSONDecoder(
- encoding=None,
- object_hook=None,
- object_pairs_hook=None,
- )
- global _default_encoder
- _default_encoder = JSONEncoder(
- skipkeys=False,
- ensure_ascii=True,
- check_circular=True,
- allow_nan=True,
- indent=None,
- separators=None,
- encoding='utf-8',
- default=None,
- )
diff --git a/wave/src/main/java/python/api/simplejson/decoder.py b/wave/src/main/java/python/api/simplejson/decoder.py
deleted file mode 100644
index 95e4f5f..0000000
--- a/wave/src/main/java/python/api/simplejson/decoder.py
+++ /dev/null
@@ -1,431 +0,0 @@
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-"""Implementation of JSONDecoder
-"""
-import re
-import sys
-import struct
-
-from scanner import make_scanner
-try:
- from _speedups import scanstring as c_scanstring
-except ImportError:
- c_scanstring = None
-
-__all__ = ['JSONDecoder']
-
-FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
-
-def _floatconstants():
- _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
- # The struct module in Python 2.4 would get frexp() out of range here
- # when an endian is specified in the format string. Fixed in Python 2.5+
- if sys.byteorder != 'big':
- _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
- nan, inf = struct.unpack('dd', _BYTES)
- return nan, inf, -inf
-
-NaN, PosInf, NegInf = _floatconstants()
-
-
-class JSONDecodeError(ValueError):
- """Subclass of ValueError with the following additional properties:
-
- msg: The unformatted error message
- doc: The JSON document being parsed
- pos: The start index of doc where parsing failed
- end: The end index of doc where parsing failed (may be None)
- lineno: The line corresponding to pos
- colno: The column corresponding to pos
- endlineno: The line corresponding to end (may be None)
- endcolno: The column corresponding to end (may be None)
-
- """
- def __init__(self, msg, doc, pos, end=None):
- ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
- self.msg = msg
- self.doc = doc
- self.pos = pos
- self.end = end
- self.lineno, self.colno = linecol(doc, pos)
- if end is not None:
- self.endlineno, self.endcolno = linecol(doc, pos)
- else:
- self.endlineno, self.endcolno = None, None
-
-
-def linecol(doc, pos):
- lineno = doc.count('\n', 0, pos) + 1
- if lineno == 1:
- colno = pos
- else:
- colno = pos - doc.rindex('\n', 0, pos)
- return lineno, colno
-
-
-def errmsg(msg, doc, pos, end=None):
- # Note that this function is called from _speedups
- lineno, colno = linecol(doc, pos)
- if end is None:
- #fmt = '{0}: line {1} column {2} (char {3})'
- #return fmt.format(msg, lineno, colno, pos)
- fmt = '%s: line %d column %d (char %d)'
- return fmt % (msg, lineno, colno, pos)
- endlineno, endcolno = linecol(doc, end)
- #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
- #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
- fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
- return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
-
-
-_CONSTANTS = {
- '-Infinity': NegInf,
- 'Infinity': PosInf,
- 'NaN': NaN,
-}
-
-STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
-BACKSLASH = {
- '"': u'"', '\\': u'\\', '/': u'/',
- 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
-}
-
-DEFAULT_ENCODING = "utf-8"
-
-def py_scanstring(s, end, encoding=None, strict=True,
- _b=BACKSLASH, _m=STRINGCHUNK.match):
- """Scan the string s for a JSON string. End is the index of the
- character in s after the quote that started the JSON string.
- Unescapes all valid JSON string escape sequences and raises ValueError
- on attempt to decode an invalid string. If strict is False then literal
- control characters are allowed in the string.
-
- Returns a tuple of the decoded string and the index of the character in s
- after the end quote."""
- if encoding is None:
- encoding = DEFAULT_ENCODING
- chunks = []
- _append = chunks.append
- begin = end - 1
- while 1:
- chunk = _m(s, end)
- if chunk is None:
- raise JSONDecodeError(
- "Unterminated string starting at", s, begin)
- end = chunk.end()
- content, terminator = chunk.groups()
- # Content is contains zero or more unescaped string characters
- if content:
- if not isinstance(content, unicode):
- content = unicode(content, encoding)
- _append(content)
- # Terminator is the end of string, a literal control character,
- # or a backslash denoting that an escape sequence follows
- if terminator == '"':
- break
- elif terminator != '\\':
- if strict:
- msg = "Invalid control character %r at" % (terminator,)
- #msg = "Invalid control character {0!r} at".format(terminator)
- raise JSONDecodeError(msg, s, end)
- else:
- _append(terminator)
- continue
- try:
- esc = s[end]
- except IndexError:
- raise JSONDecodeError(
- "Unterminated string starting at", s, begin)
- # If not a unicode escape sequence, must be in the lookup table
- if esc != 'u':
- try:
- char = _b[esc]
- except KeyError:
- msg = "Invalid \\escape: " + repr(esc)
- raise JSONDecodeError(msg, s, end)
- end += 1
- else:
- # Unicode escape sequence
- esc = s[end + 1:end + 5]
- next_end = end + 5
- if len(esc) != 4:
- msg = "Invalid \\uXXXX escape"
- raise JSONDecodeError(msg, s, end)
- uni = int(esc, 16)
- # Check for surrogate pair on UCS-4 systems
- if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
- msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
- if not s[end + 5:end + 7] == '\\u':
- raise JSONDecodeError(msg, s, end)
- esc2 = s[end + 7:end + 11]
- if len(esc2) != 4:
- raise JSONDecodeError(msg, s, end)
- uni2 = int(esc2, 16)
- uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
- next_end += 6
- char = unichr(uni)
- end = next_end
- # Append the unescaped character
- _append(char)
- return u''.join(chunks), end
-
-
-# Use speedup if available
-scanstring = c_scanstring or py_scanstring
-
-WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
-WHITESPACE_STR = ' \t\n\r'
-
-def JSONObject((s, end), encoding, strict, scan_once, object_hook,
- object_pairs_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
- pairs = []
- # Use a slice to prevent IndexError from being raised, the following
- # check will raise a more specific ValueError if the string is empty
- nextchar = s[end:end + 1]
- # Normally we expect nextchar == '"'
- if nextchar != '"':
- if nextchar in _ws:
- end = _w(s, end).end()
- nextchar = s[end:end + 1]
- # Trivial empty object
- if nextchar == '}':
- if object_pairs_hook is not None:
- result = object_pairs_hook(pairs)
- return result, end
- pairs = {}
- if object_hook is not None:
- pairs = object_hook(pairs)
- return pairs, end + 1
- elif nextchar != '"':
- raise JSONDecodeError("Expecting property name", s, end)
- end += 1
- while True:
- key, end = scanstring(s, end, encoding, strict)
-
- # To skip some function call overhead we optimize the fast paths where
- # the JSON key separator is ": " or just ":".
- if s[end:end + 1] != ':':
- end = _w(s, end).end()
- if s[end:end + 1] != ':':
- raise JSONDecodeError("Expecting : delimiter", s, end)
-
- end += 1
-
- try:
- if s[end] in _ws:
- end += 1
- if s[end] in _ws:
- end = _w(s, end + 1).end()
- except IndexError:
- pass
-
- try:
- value, end = scan_once(s, end)
- except StopIteration:
- raise JSONDecodeError("Expecting object", s, end)
- pairs.append((key, value))
-
- try:
- nextchar = s[end]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end]
- except IndexError:
- nextchar = ''
- end += 1
-
- if nextchar == '}':
- break
- elif nextchar != ',':
- raise JSONDecodeError("Expecting , delimiter", s, end - 1)
-
- try:
- nextchar = s[end]
- if nextchar in _ws:
- end += 1
- nextchar = s[end]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end]
- except IndexError:
- nextchar = ''
-
- end += 1
- if nextchar != '"':
- raise JSONDecodeError("Expecting property name", s, end - 1)
-
- if object_pairs_hook is not None:
- result = object_pairs_hook(pairs)
- return result, end
- pairs = dict(pairs)
- if object_hook is not None:
- pairs = object_hook(pairs)
- return pairs, end
-
-def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
- values = []
- nextchar = s[end:end + 1]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end:end + 1]
- # Look-ahead for trivial empty array
- if nextchar == ']':
- return values, end + 1
- _append = values.append
- while True:
- try:
- value, end = scan_once(s, end)
- except StopIteration:
- raise JSONDecodeError("Expecting object", s, end)
- _append(value)
- nextchar = s[end:end + 1]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end:end + 1]
- end += 1
- if nextchar == ']':
- break
- elif nextchar != ',':
- raise JSONDecodeError("Expecting , delimiter", s, end)
-
- try:
- if s[end] in _ws:
- end += 1
- if s[end] in _ws:
- end = _w(s, end + 1).end()
- except IndexError:
- pass
-
- return values, end
-
-class JSONDecoder(object):
- """Simple JSON <http://json.org> decoder
-
- Performs the following translations in decoding by default:
-
- +---------------+-------------------+
- | JSON | Python |
- +===============+===================+
- | object | dict |
- +---------------+-------------------+
- | array | list |
- +---------------+-------------------+
- | string | unicode |
- +---------------+-------------------+
- | number (int) | int, long |
- +---------------+-------------------+
- | number (real) | float |
- +---------------+-------------------+
- | true | True |
- +---------------+-------------------+
- | false | False |
- +---------------+-------------------+
- | null | None |
- +---------------+-------------------+
-
- It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
- their corresponding ``float`` values, which is outside the JSON spec.
-
- """
-
- def __init__(self, encoding=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, strict=True,
- object_pairs_hook=None):
- """
- *encoding* determines the encoding used to interpret any
- :class:`str` objects decoded by this instance (``'utf-8'`` by
- default). It has no effect when decoding :class:`unicode` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as :class:`unicode`.
-
- *object_hook*, if specified, will be called with the result of every
- JSON object decoded and its return value will be used in place of the
- given :class:`dict`. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- *object_pairs_hook* is an optional function that will be called with
- the result of any object literal decode with an ordered list of pairs.
- The return value of *object_pairs_hook* will be used instead of the
- :class:`dict`. This feature can be used to implement custom decoders
- that rely on the order that the key and value pairs are decoded (for
- example, :func:`collections.OrderedDict` will remember the order of
- insertion). If *object_hook* is also defined, the *object_pairs_hook*
- takes priority.
-
- *parse_float*, if specified, will be called with the string of every
- JSON float to be decoded. By default, this is equivalent to
- ``float(num_str)``. This can be used to use another datatype or parser
- for JSON floats (e.g. :class:`decimal.Decimal`).
-
- *parse_int*, if specified, will be called with the string of every
- JSON int to be decoded. By default, this is equivalent to
- ``int(num_str)``. This can be used to use another datatype or parser
- for JSON integers (e.g. :class:`float`).
-
- *parse_constant*, if specified, will be called with one of the
- following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
- can be used to raise an exception if invalid JSON numbers are
- encountered.
-
- *strict* controls the parser's behavior when it encounters an
- invalid control character in a string. The default setting of
- ``True`` means that unescaped control characters are parse errors, if
- ``False`` then control characters will be allowed in strings.
-
- """
- self.encoding = encoding
- self.object_hook = object_hook
- self.object_pairs_hook = object_pairs_hook
- self.parse_float = parse_float or float
- self.parse_int = parse_int or int
- self.parse_constant = parse_constant or _CONSTANTS.__getitem__
- self.strict = strict
- self.parse_object = JSONObject
- self.parse_array = JSONArray
- self.parse_string = scanstring
- self.scan_once = make_scanner(self)
-
- def decode(self, s, _w=WHITESPACE.match):
- """Return the Python representation of ``s`` (a ``str`` or ``unicode``
- instance containing a JSON document)
-
- """
- obj, end = self.raw_decode(s, idx=_w(s, 0).end())
- end = _w(s, end).end()
- if end != len(s):
- raise JSONDecodeError("Extra data", s, end, len(s))
- return obj
-
- def raw_decode(self, s, idx=0):
- """Decode a JSON document from ``s`` (a ``str`` or ``unicode``
- beginning with a JSON document) and return a 2-tuple of the Python
- representation and the index in ``s`` where the document ended.
-
- This can be used to decode a JSON document from a string that may
- have extraneous data at the end.
-
- """
- try:
- obj, end = self.scan_once(s, idx)
- except StopIteration:
- raise JSONDecodeError("No JSON object could be decoded", s, idx)
- return obj, end
diff --git a/wave/src/main/java/python/api/simplejson/encoder.py b/wave/src/main/java/python/api/simplejson/encoder.py
deleted file mode 100644
index 1ddf35c..0000000
--- a/wave/src/main/java/python/api/simplejson/encoder.py
+++ /dev/null
@@ -1,476 +0,0 @@
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-"""Implementation of JSONEncoder
-"""
-import re
-
-try:
- from _speedups import encode_basestring_ascii as \
- c_encode_basestring_ascii
-except ImportError:
- c_encode_basestring_ascii = None
-try:
- from _speedups import make_encoder as c_make_encoder
-except ImportError:
- c_make_encoder = None
-
-from decoder import PosInf
-
-ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
-ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
-HAS_UTF8 = re.compile(r'[\x80-\xff]')
-ESCAPE_DCT = {
- '\\': '\\\\',
- '"': '\\"',
- '\b': '\\b',
- '\f': '\\f',
- '\n': '\\n',
- '\r': '\\r',
- '\t': '\\t',
-}
-for i in range(0x20):
- #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
- ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
-
-FLOAT_REPR = repr
-
-def encode_basestring(s):
- """Return a JSON representation of a Python string
-
- """
- if isinstance(s, str) and HAS_UTF8.search(s) is not None:
- s = s.decode('utf-8')
- def replace(match):
- return ESCAPE_DCT[match.group(0)]
- return u'"' + ESCAPE.sub(replace, s) + u'"'
-
-
-def py_encode_basestring_ascii(s):
- """Return an ASCII-only JSON representation of a Python string
-
- """
- if isinstance(s, str) and HAS_UTF8.search(s) is not None:
- s = s.decode('utf-8')
- def replace(match):
- s = match.group(0)
- try:
- return ESCAPE_DCT[s]
- except KeyError:
- n = ord(s)
- if n < 0x10000:
- #return '\\u{0:04x}'.format(n)
- return '\\u%04x' % (n,)
- else:
- # surrogate pair
- n -= 0x10000
- s1 = 0xd800 | ((n >> 10) & 0x3ff)
- s2 = 0xdc00 | (n & 0x3ff)
- #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
- return '\\u%04x\\u%04x' % (s1, s2)
- return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
-
-
-encode_basestring_ascii = (
- c_encode_basestring_ascii or py_encode_basestring_ascii)
-
-class JSONEncoder(object):
- """Extensible JSON <http://json.org> encoder for Python data structures.
-
- Supports the following objects and types by default:
-
- +-------------------+---------------+
- | Python | JSON |
- +===================+===============+
- | dict | object |
- +-------------------+---------------+
- | list, tuple | array |
- +-------------------+---------------+
- | str, unicode | string |
- +-------------------+---------------+
- | int, long, float | number |
- +-------------------+---------------+
- | True | true |
- +-------------------+---------------+
- | False | false |
- +-------------------+---------------+
- | None | null |
- +-------------------+---------------+
-
- To extend this to recognize other objects, subclass and implement a
- ``.default()`` method with another method that returns a serializable
- object for ``o`` if possible, otherwise it should call the superclass
- implementation (to raise ``TypeError``).
-
- """
- item_separator = ', '
- key_separator = ': '
- def __init__(self, skipkeys=False, ensure_ascii=True,
- check_circular=True, allow_nan=True, sort_keys=False,
- indent=None, separators=None, encoding='utf-8', default=None):
- """Constructor for JSONEncoder, with sensible defaults.
-
- If skipkeys is false, then it is a TypeError to attempt
- encoding of keys that are not str, int, long, float or None. If
- skipkeys is True, such items are simply skipped.
-
- If ensure_ascii is true, the output is guaranteed to be str
- objects with all incoming unicode characters escaped. If
- ensure_ascii is false, the output will be unicode object.
-
- If check_circular is true, then lists, dicts, and custom encoded
- objects will be checked for circular references during encoding to
- prevent an infinite recursion (which would cause an OverflowError).
- Otherwise, no such check takes place.
-
- If allow_nan is true, then NaN, Infinity, and -Infinity will be
- encoded as such. This behavior is not JSON specification compliant,
- but is consistent with most JavaScript based encoders and decoders.
- Otherwise, it will be a ValueError to encode such floats.
-
- If sort_keys is true, then the output of dictionaries will be
- sorted by key; this is useful for regression tests to ensure
- that JSON serializations can be compared on a day-to-day basis.
-
- If indent is a string, then JSON array elements and object members
- will be pretty-printed with a newline followed by that string repeated
- for each level of nesting. ``None`` (the default) selects the most compact
- representation without any newlines. For backwards compatibility with
- versions of simplejson earlier than 2.1.0, an integer is also accepted
- and is converted to a string with that many spaces.
-
- If specified, separators should be a (item_separator, key_separator)
- tuple. The default is (', ', ': '). To get the most compact JSON
- representation you should specify (',', ':') to eliminate whitespace.
-
- If specified, default is a function that gets called for objects
- that can't otherwise be serialized. It should return a JSON encodable
- version of the object or raise a ``TypeError``.
-
- If encoding is not None, then all input strings will be
- transformed into unicode using that encoding prior to JSON-encoding.
- The default is UTF-8.
-
- """
-
- self.skipkeys = skipkeys
- self.ensure_ascii = ensure_ascii
- self.check_circular = check_circular
- self.allow_nan = allow_nan
- self.sort_keys = sort_keys
- if isinstance(indent, (int, long)):
- indent = ' ' * indent
- self.indent = indent
- if separators is not None:
- self.item_separator, self.key_separator = separators
- if default is not None:
- self.default = default
- self.encoding = encoding
-
- def default(self, o):
- """Implement this method in a subclass such that it returns
- a serializable object for ``o``, or calls the base implementation
- (to raise a ``TypeError``).
-
- For example, to support arbitrary iterators, you could
- implement default like this::
-
- def default(self, o):
- try:
- iterable = iter(o)
- except TypeError:
- pass
- else:
- return list(iterable)
- return JSONEncoder.default(self, o)
-
- """
- raise TypeError(repr(o) + " is not JSON serializable")
-
- def encode(self, o):
- """Return a JSON string representation of a Python data structure.
-
- >>> from simplejson import JSONEncoder
- >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
- '{"foo": ["bar", "baz"]}'
-
- """
- # This is for extremely simple cases and benchmarks.
- if isinstance(o, basestring):
- if isinstance(o, str):
- _encoding = self.encoding
- if (_encoding is not None
- and not (_encoding == 'utf-8')):
- o = o.decode(_encoding)
- if self.ensure_ascii:
- return encode_basestring_ascii(o)
- else:
- return encode_basestring(o)
- # This doesn't pass the iterator directly to ''.join() because the
- # exceptions aren't as detailed. The list call should be roughly
- # equivalent to the PySequence_Fast that ''.join() would do.
- chunks = self.iterencode(o, _one_shot=True)
- if not isinstance(chunks, (list, tuple)):
- chunks = list(chunks)
- if self.ensure_ascii:
- return ''.join(chunks)
- else:
- return u''.join(chunks)
-
- def iterencode(self, o, _one_shot=False):
- """Encode the given object and yield each string
- representation as available.
-
- For example::
-
- for chunk in JSONEncoder().iterencode(bigobject):
- mysocket.write(chunk)
-
- """
- if self.check_circular:
- markers = {}
- else:
- markers = None
- if self.ensure_ascii:
- _encoder = encode_basestring_ascii
- else:
- _encoder = encode_basestring
- if self.encoding != 'utf-8':
- def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
- if isinstance(o, str):
- o = o.decode(_encoding)
- return _orig_encoder(o)
-
- def floatstr(o, allow_nan=self.allow_nan,
- _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
- # Check for specials. Note that this type of test is processor
- # and/or platform-specific, so do tests which don't depend on
- # the internals.
-
- if o != o:
- text = 'NaN'
- elif o == _inf:
- text = 'Infinity'
- elif o == _neginf:
- text = '-Infinity'
- else:
- return _repr(o)
-
- if not allow_nan:
- raise ValueError(
- "Out of range float values are not JSON compliant: " +
- repr(o))
-
- return text
-
-
- if (_one_shot and c_make_encoder is not None
- and not self.indent and not self.sort_keys):
- _iterencode = c_make_encoder(
- markers, self.default, _encoder, self.indent,
- self.key_separator, self.item_separator, self.sort_keys,
- self.skipkeys, self.allow_nan)
- else:
- _iterencode = _make_iterencode(
- markers, self.default, _encoder, self.indent, floatstr,
- self.key_separator, self.item_separator, self.sort_keys,
- self.skipkeys, _one_shot)
- return _iterencode(o, 0)
-
-def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
- _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
- ## HACK: hand-optimized bytecode; turn globals into locals
- False=False,
- True=True,
- ValueError=ValueError,
- basestring=basestring,
- dict=dict,
- float=float,
- id=id,
- int=int,
- isinstance=isinstance,
- list=list,
- long=long,
- str=str,
- tuple=tuple,
- ):
-
- def _iterencode_list(lst, _current_indent_level):
- if not lst:
- yield '[]'
- return
- if markers is not None:
- markerid = id(lst)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = lst
- buf = '['
- if _indent is not None:
- _current_indent_level += 1
- newline_indent = '\n' + (_indent * _current_indent_level)
- separator = _item_separator + newline_indent
- buf += newline_indent
- else:
- newline_indent = None
- separator = _item_separator
- first = True
- for value in lst:
- if first:
- first = False
- else:
- buf = separator
- if isinstance(value, basestring):
- yield buf + _encoder(value)
- elif value is None:
- yield buf + 'null'
- elif value is True:
- yield buf + 'true'
- elif value is False:
- yield buf + 'false'
- elif isinstance(value, (int, long)):
- yield buf + str(value)
- elif isinstance(value, float):
- yield buf + _floatstr(value)
- else:
- yield buf
- if isinstance(value, (list, tuple)):
- chunks = _iterencode_list(value, _current_indent_level)
- elif isinstance(value, dict):
- chunks = _iterencode_dict(value, _current_indent_level)
- else:
- chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
- if newline_indent is not None:
- _current_indent_level -= 1
- yield '\n' + (_indent * _current_indent_level)
- yield ']'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode_dict(dct, _current_indent_level):
- if not dct:
- yield '{}'
- return
- if markers is not None:
- markerid = id(dct)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = dct
- yield '{'
- if _indent is not None:
- _current_indent_level += 1
- newline_indent = '\n' + (_indent * _current_indent_level)
- item_separator = _item_separator + newline_indent
- yield newline_indent
- else:
- newline_indent = None
- item_separator = _item_separator
- first = True
- if _sort_keys:
- items = dct.items()
- items.sort(key=lambda kv: kv[0])
- else:
- items = dct.iteritems()
- for key, value in items:
- if isinstance(key, basestring):
- pass
- # JavaScript is weakly typed for these, so it makes sense to
- # also allow them. Many encoders seem to do something like this.
- elif isinstance(key, float):
- key = _floatstr(key)
- elif key is True:
- key = 'true'
- elif key is False:
- key = 'false'
- elif key is None:
- key = 'null'
- elif isinstance(key, (int, long)):
- key = str(key)
- elif _skipkeys:
- continue
- else:
- raise TypeError("key " + repr(key) + " is not a string")
- if first:
- first = False
- else:
- yield item_separator
- yield _encoder(key)
- yield _key_separator
- if isinstance(value, basestring):
- yield _encoder(value)
- elif value is None:
- yield 'null'
- elif value is True:
- yield 'true'
- elif value is False:
- yield 'false'
- elif isinstance(value, (int, long)):
- yield str(value)
- elif isinstance(value, float):
- yield _floatstr(value)
- else:
- if isinstance(value, (list, tuple)):
- chunks = _iterencode_list(value, _current_indent_level)
- elif isinstance(value, dict):
- chunks = _iterencode_dict(value, _current_indent_level)
- else:
- chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
- if newline_indent is not None:
- _current_indent_level -= 1
- yield '\n' + (_indent * _current_indent_level)
- yield '}'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode(o, _current_indent_level):
- if isinstance(o, basestring):
- yield _encoder(o)
- elif o is None:
- yield 'null'
- elif o is True:
- yield 'true'
- elif o is False:
- yield 'false'
- elif isinstance(o, (int, long)):
- yield str(o)
- elif isinstance(o, float):
- yield _floatstr(o)
- elif isinstance(o, (list, tuple)):
- for chunk in _iterencode_list(o, _current_indent_level):
- yield chunk
- elif isinstance(o, dict):
- for chunk in _iterencode_dict(o, _current_indent_level):
- yield chunk
- else:
- if markers is not None:
- markerid = id(o)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = o
- o = _default(o)
- for chunk in _iterencode(o, _current_indent_level):
- yield chunk
- if markers is not None:
- del markers[markerid]
-
- return _iterencode
diff --git a/wave/src/main/java/python/api/simplejson/jsonfilter.py b/wave/src/main/java/python/api/simplejson/jsonfilter.py
deleted file mode 100644
index 7c0da57..0000000
--- a/wave/src/main/java/python/api/simplejson/jsonfilter.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-import simplejson
-import cgi
-
-class JSONFilter(object):
- def __init__(self, app, mime_type='text/x-json'):
- self.app = app
- self.mime_type = mime_type
-
- def __call__(self, environ, start_response):
- # Read JSON POST input to jsonfilter.json if matching mime type
- response = {'status': '200 OK', 'headers': []}
- def json_start_response(status, headers):
- response['status'] = status
- response['headers'].extend(headers)
- environ['jsonfilter.mime_type'] = self.mime_type
- if environ.get('REQUEST_METHOD', '') == 'POST':
- if environ.get('CONTENT_TYPE', '') == self.mime_type:
- args = [_ for _ in [environ.get('CONTENT_LENGTH')] if _]
- data = environ['wsgi.input'].read(*map(int, args))
- environ['jsonfilter.json'] = simplejson.loads(data)
- res = simplejson.dumps(self.app(environ, json_start_response))
- jsonp = cgi.parse_qs(environ.get('QUERY_STRING', '')).get('jsonp')
- if jsonp:
- content_type = 'text/javascript'
- res = ''.join(jsonp + ['(', res, ')'])
- elif 'Opera' in environ.get('HTTP_USER_AGENT', ''):
- # Opera has bunk XMLHttpRequest support for most mime types
- content_type = 'text/plain'
- else:
- content_type = self.mime_type
- headers = [
- ('Content-type', content_type),
- ('Content-length', len(res)),
- ]
- headers.extend(response['headers'])
- start_response(response['status'], headers)
- return [res]
-
-def factory(app, global_conf, **kw):
- return JSONFilter(app, **kw)
diff --git a/wave/src/main/java/python/api/simplejson/ordered_dict.py b/wave/src/main/java/python/api/simplejson/ordered_dict.py
deleted file mode 100644
index a186e77..0000000
--- a/wave/src/main/java/python/api/simplejson/ordered_dict.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
-
-http://code.activestate.com/recipes/576693/
-
-"""
-from UserDict import DictMixin
-
-# Modified from original to support Python 2.4, see
-# http://code.google.com/p/simplejson/issues/detail?id=53
-try:
- all
-except NameError:
- def all(seq):
- for elem in seq:
- if not elem:
- return False
- return True
-
-class OrderedDict(dict, DictMixin):
-
- def __init__(self, *args, **kwds):
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__end
- except AttributeError:
- self.clear()
- self.update(*args, **kwds)
-
- def clear(self):
- self.__end = end = []
- end += [None, end, end] # sentinel node for doubly linked list
- self.__map = {} # key --> [key, prev, next]
- dict.clear(self)
-
- def __setitem__(self, key, value):
- if key not in self:
- end = self.__end
- curr = end[1]
- curr[2] = end[1] = self.__map[key] = [key, curr, end]
- dict.__setitem__(self, key, value)
-
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- key, prev, next = self.__map.pop(key)
- prev[2] = next
- next[1] = prev
-
- def __iter__(self):
- end = self.__end
- curr = end[2]
- while curr is not end:
- yield curr[0]
- curr = curr[2]
-
- def __reversed__(self):
- end = self.__end
- curr = end[1]
- while curr is not end:
- yield curr[0]
- curr = curr[1]
-
- def popitem(self, last=True):
- if not self:
- raise KeyError('dictionary is empty')
- # Modified from original to support Python 2.4, see
- # http://code.google.com/p/simplejson/issues/detail?id=53
- if last:
- key = reversed(self).next()
- else:
- key = iter(self).next()
- value = self.pop(key)
- return key, value
-
- def __reduce__(self):
- items = [[k, self[k]] for k in self]
- tmp = self.__map, self.__end
- del self.__map, self.__end
- inst_dict = vars(self).copy()
- self.__map, self.__end = tmp
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def keys(self):
- return list(self)
-
- setdefault = DictMixin.setdefault
- update = DictMixin.update
- pop = DictMixin.pop
- values = DictMixin.values
- items = DictMixin.items
- iterkeys = DictMixin.iterkeys
- itervalues = DictMixin.itervalues
- iteritems = DictMixin.iteritems
-
- def __repr__(self):
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
-
- def copy(self):
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- if isinstance(other, OrderedDict):
- return len(self)==len(other) and \
- all(p==q for p, q in zip(self.items(), other.items()))
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
diff --git a/wave/src/main/java/python/api/simplejson/scanner.py b/wave/src/main/java/python/api/simplejson/scanner.py
deleted file mode 100644
index 4e01271..0000000
--- a/wave/src/main/java/python/api/simplejson/scanner.py
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-"""JSON token scanner
-"""
-import re
-try:
- from simplejson._speedups import make_scanner as c_make_scanner
-except ImportError:
- c_make_scanner = None
-
-__all__ = ['make_scanner']
-
-NUMBER_RE = re.compile(
- r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
- (re.VERBOSE | re.MULTILINE | re.DOTALL))
-
-def py_make_scanner(context):
- parse_object = context.parse_object
- parse_array = context.parse_array
- parse_string = context.parse_string
- match_number = NUMBER_RE.match
- encoding = context.encoding
- strict = context.strict
- parse_float = context.parse_float
- parse_int = context.parse_int
- parse_constant = context.parse_constant
- object_hook = context.object_hook
- object_pairs_hook = context.object_pairs_hook
-
- def _scan_once(string, idx):
- try:
- nextchar = string[idx]
- except IndexError:
- raise StopIteration
-
- if nextchar == '"':
- return parse_string(string, idx + 1, encoding, strict)
- elif nextchar == '{':
- return parse_object((string, idx + 1), encoding, strict,
- _scan_once, object_hook, object_pairs_hook)
- elif nextchar == '[':
- return parse_array((string, idx + 1), _scan_once)
- elif nextchar == 'n' and string[idx:idx + 4] == 'null':
- return None, idx + 4
- elif nextchar == 't' and string[idx:idx + 4] == 'true':
- return True, idx + 4
- elif nextchar == 'f' and string[idx:idx + 5] == 'false':
- return False, idx + 5
-
- m = match_number(string, idx)
- if m is not None:
- integer, frac, exp = m.groups()
- if frac or exp:
- res = parse_float(integer + (frac or '') + (exp or ''))
- else:
- res = parse_int(integer)
- return res, m.end()
- elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
- return parse_constant('NaN'), idx + 3
- elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
- return parse_constant('Infinity'), idx + 8
- elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
- return parse_constant('-Infinity'), idx + 9
- else:
- raise StopIteration
-
- return _scan_once
-
-make_scanner = c_make_scanner or py_make_scanner
diff --git a/wave/src/main/java/python/api/simplejson/tool.py b/wave/src/main/java/python/api/simplejson/tool.py
deleted file mode 100644
index 646d254..0000000
--- a/wave/src/main/java/python/api/simplejson/tool.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-r"""Command-line tool to validate and pretty-print JSON
-
-Usage::
-
- $ echo '{"json":"obj"}' | python -m simplejson.tool
- {
- "json": "obj"
- }
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
- Expecting property name: line 1 column 2 (char 2)
-
-"""
-import sys
-import simplejson as json
-
-def main():
- if len(sys.argv) == 1:
- infile = sys.stdin
- outfile = sys.stdout
- elif len(sys.argv) == 2:
- infile = open(sys.argv[1], 'rb')
- outfile = sys.stdout
- elif len(sys.argv) == 3:
- infile = open(sys.argv[1], 'rb')
- outfile = open(sys.argv[2], 'wb')
- else:
- raise SystemExit(sys.argv[0] + " [infile [outfile]]")
- try:
- obj = json.load(infile, object_pairs_hook=json.OrderedDict)
- except ValueError, e:
- raise SystemExit(e)
- json.dump(obj, outfile, sort_keys=True, indent=' ')
- outfile.write('\n')
-
-
-if __name__ == '__main__':
- main()
diff --git a/wave/src/main/java/python/api/testdata.py b/wave/src/main/java/python/api/testdata.py
deleted file mode 100644
index fd6b248..0000000
--- a/wave/src/main/java/python/api/testdata.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-json_string = """{
- "wavelet": {
- "creationTime": 1277873815463,
- "lastModifiedTime": 1277873843515,
- "version": 51,
- "participants": [
- "pamela.fox@wavesandbox.com",
- "5.latest.exporty-bot@appspot.com"
- ],
- "participantRoles": {
- "5.latest.exporty-bot@appspot.com": "FULL",
- "pamela.fox@wavesandbox.com": "FULL"
- },
- "dataDocuments": {
- },
- "tags": [
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "rootBlipId": "b+IvD7RCuWB",
- "title": "Test",
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "rootThread": {
- "id": "",
- "location": -1,
- "blipIds": [
- "b+IvD7RCuWB",
- "b+IvD7RCuWC",
- "b+IvD7RCuWE"
- ]
- }
- },
- "blips": {
- "b+IvD7RCuWB": {
- "annotations": [],
- "elements": {},
- "blipId": "b+IvD7RCuWB",
- "childBlipIds": [
- "b+IvD7RCuWD"
- ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "test",
- "lastModifiedTime": 1277873815441,
- "parentBlipId": null,
- "version": 5,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- "b+IvD7RCuWD"
- ],
- "threadId": ""
- },
- "b+IvD7RCuWC": {
- "annotations": [
- ],
- "elements": {
- },
- "blipId": "b+IvD7RCuWC",
- "childBlipIds": [
- "b+IvD7RCuWF",
- "b+IvD7RCuWG",
- "b+IvD7RCuWH"
- ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "\\ntest",
- "lastModifiedTime": 1277873818083,
- "parentBlipId": null,
- "version": 10,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- "b+IvD7RCuWF"
- ],
- "threadId": ""
- },
- "b+IvD7RCuWH": {
- "annotations": [
- ],
- "elements": {
- },
- "blipId": "b+IvD7RCuWH",
- "childBlipIds": [
- ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "\\ntest",
- "lastModifiedTime": 1277873830724,
- "parentBlipId": "b+IvD7RCuWC",
- "version": 48,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- ],
- "threadId": "b+IvD7RCuWF"
- },
- "b+IvD7RCuWF": {
- "annotations": [
- ],
- "elements": {
- },
- "blipId": "b+IvD7RCuWF",
- "childBlipIds": [
- ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "\\ntest\\n",
- "lastModifiedTime": 1277873824775,
- "parentBlipId": "b+IvD7RCuWC",
- "version": 32,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- ],
- "threadId": "b+IvD7RCuWF"
- },
- "b+IvD7RCuWG": {
- "annotations": [
- ],
- "elements": {
- },
- "blipId": "b+IvD7RCuWG",
- "childBlipIds": [
- ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "\\ntest\\n",
- "lastModifiedTime": 1277873828985,
- "parentBlipId": "b+IvD7RCuWC",
- "version": 41,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- ],
- "threadId": "b+IvD7RCuWF"
- },
- "b+IvD7RCuWD": {
- "annotations": [],
- "elements": { },
- "blipId": "b+IvD7RCuWD",
- "childBlipIds": [ ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "\\ntest",
- "lastModifiedTime": 1277873819390,
- "parentBlipId": "b+IvD7RCuWB",
- "version": 18,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- ],
- "threadId": "b+IvD7RCuWD"
- },
- "b+IvD7RCuWE": {
- "annotations": [],
- "elements": {},
- "blipId": "b+IvD7RCuWE",
- "childBlipIds": [
- ],
- "contributors": [
- "pamela.fox@wavesandbox.com"
- ],
- "creator": "pamela.fox@wavesandbox.com",
- "content": "\\nTest",
- "lastModifiedTime": 1277873822476,
- "parentBlipId": null,
- "version": 26,
- "waveId": "wavesandbox.com!w+IvD7RCuWA",
- "waveletId": "wavesandbox.com!conv+root",
- "replyThreadIds": [
- ],
- "threadId": ""
- }
- },
- "threads": {
- "b+IvD7RCuWF": {
- "id": "b+IvD7RCuWF",
- "location": -1,
- "blipIds": [
- "b+IvD7RCuWF",
- "b+IvD7RCuWG",
- "b+IvD7RCuWH"
- ]
- },
- "b+IvD7RCuWD": {
- "id": "b+IvD7RCuWD",
- "location": -1,
- "blipIds": [
- "b+IvD7RCuWD"
- ]
- }
- },
- "robotAddress": "5.latest.exporty-bot@appspot.com"
-}"""
diff --git a/wave/src/main/java/python/api/util.py b/wave/src/main/java/python/api/util.py
deleted file mode 100644
index ab7bc2c..0000000
--- a/wave/src/main/java/python/api/util.py
+++ /dev/null
@@ -1,198 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Utility library containing various helpers used by the API."""
-
-import re
-
-CUSTOM_SERIALIZE_METHOD_NAME = 'serialize'
-
-MARKUP_RE = re.compile(r'<([^>]*?)>')
-RESERVED_PROXY_FOR_CHARS_RE = re.compile(ur'[\s\u0000-\u001F@,:<>\u007F]')
-
-def force_unicode(object):
- """ Return the Unicode string version of object, with UTF-8 encoding. """
- if isinstance(object, unicode):
- return object
- return unicode(str(object), 'utf-8')
-
-def parse_markup(markup):
- """Parses a bit of markup into robot compatible text.
-
- For now this is a rough approximation.
- """
- def replace_tag(group):
- if not group.groups:
- return ''
- tag = group.groups()[0].split(' ', 1)[0]
- if (tag == 'p' or tag == 'br'):
- return '\n'
- return ''
-
- return MARKUP_RE.sub(replace_tag, markup)
-
-def is_iterable(inst):
- """Returns whether or not this is a list, tuple, set or dict .
-
- Note that this does not return true for strings.
- """
- return hasattr(inst, '__iter__')
-
-def is_dict(inst):
- """Returns whether or not the specified instance is a dict."""
- return hasattr(inst, 'iteritems')
-
-
-def is_user_defined_new_style_class(obj):
- """Returns whether or not the specified instance is a user-defined type."""
- return type(obj).__module__ != '__builtin__'
-
-def lower_camel_case(s):
- """Converts a string to lower camel case.
-
- Examples:
- foo => foo
- foo_bar => fooBar
- foo__bar => fooBar
- foo_bar_baz => fooBarBaz
-
- Args:
- s: The string to convert to lower camel case.
-
- Returns:
- The lower camel cased string.
- """
- return reduce(lambda a, b: a + (a and b.capitalize() or b), s.split('_'))
-
-def non_none_dict(d):
- """return a copy of the dictionary without none values."""
- return dict([a for a in d.items() if a[1] is not None])
-
-def _serialize_attributes(obj):
- """Serializes attributes of an instance.
-
- Iterates all attributes of an object and invokes serialize if they are
- public and not callable.
-
- Args:
- obj: The instance to serialize.
-
- Returns:
- The serialized object.
- """
- data = {}
- for attr_name in dir(obj):
- if attr_name.startswith('_'):
- continue
- attr = getattr(obj, attr_name)
- if attr is None or callable(attr):
- continue
- # Looks okay, serialize it.
- data[lower_camel_case(attr_name)] = serialize(attr)
- return data
-
-
-def _serialize_dict(d):
- """Invokes serialize on all of its key/value pairs.
-
- Args:
- d: The dict instance to serialize.
-
- Returns:
- The serialized dict.
- """
- data = {}
- for k, v in d.items():
- data[lower_camel_case(k)] = serialize(v)
- return data
-
-
-def serialize(obj):
- """Serializes any instance.
-
- If this is a user-defined instance
- type, it will first check for a custom Serialize() function and use that
- if it exists. Otherwise, it will invoke serialize all of its public
- attributes. Lists and dicts are serialized trivially.
-
- Args:
- obj: The instance to serialize.
-
- Returns:
- The serialized object.
- """
- if is_user_defined_new_style_class(obj):
- if obj and hasattr(obj, CUSTOM_SERIALIZE_METHOD_NAME):
- method = getattr(obj, CUSTOM_SERIALIZE_METHOD_NAME)
- if callable(method):
- return method()
- return _serialize_attributes(obj)
- elif is_dict(obj):
- return _serialize_dict(obj)
- elif is_iterable(obj):
- return [serialize(v) for v in obj]
- return obj
-
-def is_valid_proxy_for_id(s):
- """ Checks if the given string is a valid proxy id.
-
- This method asserts whether the string contains reserved characters or not.
- This check is to ensure that when we concatenate the robot id and the proxy
- id, it doesn't result in an invalid participant id.
-
- The reserved characters are:
- - whitespaces
- - non-printing characters: decimal 0 - 31 (hex 00 - 1F), and decimal 127
- (hex 7F)
- - @, comma, :, <, and >
- If you need to pass in an arbitrary string as the proxy id, please consider
- encoding the string with a URL encoder (for example, urllib.quote_plus) or
- base64 encoder (for example, base64.b64encode) first.
-
- Args:
- s: the string to be checked.
-
- Returns:
- True if the string is a valid proxy for id.
- """
- return RESERVED_PROXY_FOR_CHARS_RE.search(s) == None
-
-def check_is_valid_proxy_for_id(s):
- """ Checks if the given string is a valid proxy id.
-
- Please see isValidProxyForId(String) for more details on the assertion. This
- method raises a ValueError exception if the input string is not a valid proxy
- id.
-
- Args:
- s: the string to be checked.
- """
- if s != None and not is_valid_proxy_for_id(s):
- raise ValueError(s + ' is not a valid proxy for id.')
-
-class StringEnum(object):
- """Enum like class that is configured with a list of values.
-
- This class effectively implements an enum for Elements, except for that
- the actual values of the enums will be the string values.
- """
-
- def __init__(self, *values):
- for name in values:
- setattr(self, name, name)
diff --git a/wave/src/main/java/python/api/util_test.py b/wave/src/main/java/python/api/util_test.py
deleted file mode 100644
index 4682821..0000000
--- a/wave/src/main/java/python/api/util_test.py
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the util module."""
-
-
-__author__ = 'davidbyttow@google.com (David Byttow)'
-
-
-import unittest
-import urllib
-
-import ops
-import util
-
-
-class TestUtils(unittest.TestCase):
- """Tests utility functions."""
-
- def testIsIterable(self):
- self.assertTrue(util.is_iterable([]))
- self.assertTrue(util.is_iterable({}))
- self.assertTrue(util.is_iterable(set()))
- self.assertTrue(util.is_iterable(()))
- self.assertFalse(util.is_iterable(42))
- self.assertFalse(util.is_iterable('list?'))
- self.assertFalse(util.is_iterable(object))
-
- def testIsDict(self):
- self.assertFalse(util.is_dict([]))
- self.assertTrue(util.is_dict({}))
- self.assertFalse(util.is_dict(set()))
- self.assertFalse(util.is_dict(()))
- self.assertFalse(util.is_dict(42))
- self.assertFalse(util.is_dict('dict?'))
- self.assertFalse(util.is_dict(object))
-
- def testIsUserDefinedNewStyleClass(self):
- class OldClass:
- pass
-
- class NewClass(object):
- pass
-
- self.assertFalse(util.is_user_defined_new_style_class(OldClass()))
- self.assertTrue(util.is_user_defined_new_style_class(NewClass()))
- self.assertFalse(util.is_user_defined_new_style_class({}))
- self.assertFalse(util.is_user_defined_new_style_class(()))
- self.assertFalse(util.is_user_defined_new_style_class(42))
- self.assertFalse(util.is_user_defined_new_style_class('instance?'))
-
- def testLowerCamelCase(self):
- self.assertEquals('foo', util.lower_camel_case('foo'))
- self.assertEquals('fooBar', util.lower_camel_case('foo_bar'))
- self.assertEquals('fooBar', util.lower_camel_case('fooBar'))
- self.assertEquals('blipId', util.lower_camel_case('blip_id'))
- self.assertEquals('fooBar', util.lower_camel_case('foo__bar'))
- self.assertEquals('fooBarBaz', util.lower_camel_case('foo_bar_baz'))
- self.assertEquals('f', util.lower_camel_case('f'))
- self.assertEquals('f', util.lower_camel_case('f_'))
- self.assertEquals('', util.lower_camel_case(''))
- self.assertEquals('', util.lower_camel_case('_'))
- self.assertEquals('aBCDEF', util.lower_camel_case('_a_b_c_d_e_f_'))
-
- def assertListsEqual(self, a, b):
- self.assertEquals(len(a), len(b))
- for i in range(len(a)):
- self.assertEquals(a[i], b[i])
-
- def assertDictsEqual(self, a, b):
- self.assertEquals(len(a.keys()), len(b.keys()))
- for k, v in a.iteritems():
- self.assertEquals(v, b[k])
-
- def assertNotRaises(self, exception, function, *args):
- try:
- function(*args)
- except ValueError:
- fail()
-
- def testSerializeList(self):
- data = [1, 2, 3]
- output = util.serialize(data)
- self.assertListsEqual(data, output)
-
- def testSerializeDict(self):
- data = {'key': 'value', 'under_score': 'value2'}
- expected = {'key': 'value', 'underScore': 'value2'}
- output = util.serialize(data)
- self.assertDictsEqual(expected, output)
-
- def testNonNoneDict(self):
- a = {'a': 1, 'b': 1}
- self.assertDictsEqual(a, util.non_none_dict(a))
- b = a.copy()
- b['c'] = None
- self.assertDictsEqual(a, util.non_none_dict(b))
-
- def testForceUnicode(self):
- self.assertEquals(u"aaa", util.force_unicode("aaa"))
- self.assertEquals(u"12", util.force_unicode(12))
- self.assertEquals(u"\u0430\u0431\u0432",
- util.force_unicode("\xd0\xb0\xd0\xb1\xd0\xb2"))
- self.assertEquals(u'\u30e6\u30cb\u30b3\u30fc\u30c9',
- util.force_unicode(u'\u30e6\u30cb\u30b3\u30fc\u30c9'))
-
- def testSerializeAttributes(self):
-
- class Data(object):
- def __init__(self):
- self.public = 1
- self._protected = 2
- self.__private = 3
-
- def Func(self):
- pass
-
- data = Data()
- output = util.serialize(data)
- # Functions and non-public fields should not be serialized.
- self.assertEquals(1, len(output.keys()))
- self.assertEquals(data.public, output['public'])
-
- def testStringEnum(self):
- util.StringEnum()
- single = util.StringEnum('foo')
- self.assertEquals('foo', single.foo)
- multi = util.StringEnum('foo', 'bar')
- self.assertEquals('foo', multi.foo)
- self.assertEquals('bar', multi.bar)
-
- def testParseMarkup(self):
- self.assertEquals('foo', util.parse_markup('foo'))
- self.assertEquals('foo bar', util.parse_markup('foo <b>bar</b>'))
- self.assertEquals('foo\nbar', util.parse_markup('foo<br>bar'))
- self.assertEquals('foo\nbar', util.parse_markup('foo<p indent="3">bar'))
-
- def testIsValidProxyForId(self):
- self.assertTrue(util.is_valid_proxy_for_id(''))
- self.assertTrue(util.is_valid_proxy_for_id('proxyid'))
- self.assertTrue(util.is_valid_proxy_for_id('proxy-id1+gmail.com'))
- self.assertTrue(util.is_valid_proxy_for_id('proxy-id1_at_gmail.com'))
- self.assertTrue(util.is_valid_proxy_for_id('proxy-id%201_at_gmail.com'))
- self.assertTrue(util.is_valid_proxy_for_id(urllib.quote('proxyid@bar.com')))
-
- self.assertFalse(util.is_valid_proxy_for_id('proxy id1'))
- self.assertFalse(util.is_valid_proxy_for_id(u'proxy\u0000id1'))
- self.assertFalse(util.is_valid_proxy_for_id(u'proxy\u0009id1'))
- self.assertFalse(util.is_valid_proxy_for_id(u'proxy\u001Fid1'))
- self.assertFalse(util.is_valid_proxy_for_id('proxy@id'))
- self.assertFalse(util.is_valid_proxy_for_id('proxy,id'))
- self.assertFalse(util.is_valid_proxy_for_id('proxy:id'))
- self.assertFalse(util.is_valid_proxy_for_id('proxy<id'))
- self.assertFalse(util.is_valid_proxy_for_id('proxy>id'))
- self.assertFalse(util.is_valid_proxy_for_id(u'proxy\u007Fid'))
-
- def testCheckIsValidProxyForId(self):
- self.assertRaises(ValueError, util.check_is_valid_proxy_for_id,
- 'foo@bar.com')
- self.assertNotRaises(ValueError, util.check_is_valid_proxy_for_id,
- None)
- self.assertNotRaises(ValueError, util.check_is_valid_proxy_for_id,
- '')
- self.assertNotRaises(ValueError, util.check_is_valid_proxy_for_id,
- 'foo+bar.com')
- self.assertNotRaises(ValueError, util.check_is_valid_proxy_for_id,
- urllib.quote('foo@bar.com'))
- self.assertNotRaises(ValueError, util.check_is_valid_proxy_for_id, None)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/wavelet.py b/wave/src/main/java/python/api/wavelet.py
deleted file mode 100644
index c3352e4..0000000
--- a/wave/src/main/java/python/api/wavelet.py
+++ /dev/null
@@ -1,481 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Defines classes that are needed to model a wavelet."""
-
-import blip
-import errors
-import util
-
-
-class DataDocs(object):
- """Class modeling a bunch of data documents in pythonic way."""
-
- def __init__(self, init_docs, wave_id, wavelet_id, operation_queue):
- self._docs = init_docs
- self._wave_id = wave_id
- self._wavelet_id = wavelet_id
- self._operation_queue = operation_queue
-
- def __iter__(self):
- return self._docs.__iter__()
-
- def __contains__(self, key):
- return key in self._docs
-
- def __delitem__(self, key):
- if not key in self._docs:
- return
- self._operation_queue.wavelet_datadoc_set(
- self._wave_id, self._wavelet_id, key, None)
- del self._docs[key]
-
- def __getitem__(self, key):
- return self._docs[key]
-
- def __setitem__(self, key, value):
- self._operation_queue.wavelet_datadoc_set(
- self._wave_id, self._wavelet_id, key, value)
- if value is None and key in self._docs:
- del self._docs[key]
- else:
- self._docs[key] = value
-
- def __len__(self):
- return len(self._docs)
-
- def keys(self):
- return self._docs.keys()
-
- def serialize(self):
- """Returns a dictionary of the data documents."""
- return self._docs
-
-
-class Participants(object):
- """Class modelling a set of participants in pythonic way."""
-
- #: Designates full access (read/write) role.
- ROLE_FULL = "FULL"
-
- #: Designates read-only role.
- ROLE_READ_ONLY = "READ_ONLY"
-
- def __init__(self, participants, roles, wave_id, wavelet_id, operation_queue):
- self._participants = set(participants)
- self._roles = roles.copy()
- self._wave_id = wave_id
- self._wavelet_id = wavelet_id
- self._operation_queue = operation_queue
-
- def __contains__(self, participant):
- return participant in self._participants
-
- def __len__(self):
- return len(self._participants)
-
- def __iter__(self):
- return self._participants.__iter__()
-
- def add(self, participant_id):
- """Adds a participant by their ID (address)."""
- self._operation_queue.wavelet_add_participant(
- self._wave_id, self._wavelet_id, participant_id)
- self._participants.add(participant_id)
-
- def get_role(self, participant_id):
- """Return the role for the given participant_id."""
- return self._roles.get(participant_id, Participants.ROLE_FULL)
-
- def set_role(self, participant_id, role):
- """Sets the role for the given participant_id."""
- if role != Participants.ROLE_FULL and role != Participants.ROLE_READ_ONLY:
- raise ValueError(role + ' is not a valid role')
- self._operation_queue.wavelet_modify_participant_role(
- self._wave_id, self._wavelet_id, participant_id, role)
- self._roles[participant_id] = role
-
- def serialize(self):
- """Returns a list of the participants."""
- return list(self._participants)
-
-
-class Tags(object):
- """Class modelling a list of tags."""
- def __init__(self, tags, wave_id, wavelet_id, operation_queue):
- self._tags = list(tags)
- self._wave_id = wave_id
- self._wavelet_id = wavelet_id
- self._operation_queue = operation_queue
-
- def __getitem__(self, index):
- return self._tags[index]
-
- def __len__(self):
- return len(self._tags)
-
- def __iter__(self):
- return self._tags.__iter__()
-
- def append(self, tag):
- """Appends a tag if it doesn't already exist."""
- tag = util.force_unicode(tag)
- if tag in self._tags:
- return
- self._operation_queue.wavelet_modify_tag(
- self._wave_id, self._wavelet_id, tag)
- self._tags.append(tag)
-
- def remove(self, tag):
- """Removes a tag if it exists."""
- tag = util.force_unicode(tag)
- if not tag in self._tags:
- return
- self._operation_queue.wavelet_modify_tag(
- self._wave_id, self._wavelet_id, tag, modify_how='remove')
- self._tags.remove(tag)
-
- def serialize(self):
- """Returns a list of tags."""
- return list(self._tags)
-
-
-class BlipThread(object):
- """ Models a group of blips in a wave."""
-
- def __init__(self, id, location, blip_ids, all_blips, operation_queue):
- self._id = id
- self._location = location
- self._blip_ids = blip_ids
- self._all_blips = all_blips
- self._operation_queue = operation_queue
-
- @property
- def id(self):
- """Returns this thread's id."""
- return self._id
-
- @property
- def location(self):
- """Returns this thread's location."""
- return self._location
-
- @property
- def blip_ids(self):
- """Returns the blip IDs in this thread."""
- return self._blip_ids
-
- @property
- def blips(self):
- """Returns the blips in this thread."""
- blips = []
- for blip_id in self._blip_ids:
- blips.append(self._all_blips[blip_id])
- return blips
-
- def _add_internal(self, blip):
- """Adds a blip to the thread, sends out no operations."""
- self._blip_ids.append(blip.blip_id)
- self._all_blips[blip.blip_id] = blip
-
- def serialize(self):
- """ Returns serialized properties."""
- return {'id': self._id,
- 'location': self._location,
- 'blipIds': self._blip_ids}
-
-
-class Wavelet(object):
- """Models a single wavelet.
-
- A single wavelet is composed of metadata, participants, and its blips.
- To guarantee that all blips are available, specify Context.ALL for events.
- """
-
- def __init__(self, json, blips, root_thread, operation_queue, raw_deltas=None):
- """Inits this wavelet with JSON data.
-
- Args:
- json: JSON data dictionary from Wave server.
- blips: a dictionary object that can be used to resolve blips.
- root_thread: a BlipThread object containing the blips in the root thread.
- operation_queue: an OperationQueue object to be used to
- send any generated operations to.
- """
- self._operation_queue = operation_queue
- self._root_thread = root_thread
- self._wave_id = json.get('waveId')
- self._wavelet_id = json.get('waveletId')
- self._creator = json.get('creator')
- self._raw_deltas = raw_deltas
- self._raw_snapshot = json.get('rawSnapshot')
- self._creation_time = json.get('creationTime', 0)
- self._data_documents = DataDocs(json.get('dataDocuments', {}),
- self._wave_id,
- self._wavelet_id,
- operation_queue)
- self._last_modified_time = json.get('lastModifiedTime')
- self._participants = Participants(json.get('participants', []),
- json.get('participantRoles', {}),
- self._wave_id,
- self._wavelet_id,
- operation_queue)
- self._title = json.get('title', '')
- self._tags = Tags(json.get('tags', []),
- self._wave_id,
- self._wavelet_id,
- operation_queue)
-
- self._raw_data = json
- self._blips = blip.Blips(blips)
- self._root_blip_id = json.get('rootBlipId')
- if self._root_blip_id and self._root_blip_id in self._blips:
- self._root_blip = self._blips[self._root_blip_id]
- else:
- self._root_blip = None
- self._robot_address = None
-
- @property
- def wavelet_id(self):
- """Returns this wavelet's id."""
- return self._wavelet_id
-
- @property
- def wave_id(self):
- """Returns this wavelet's parent wave id."""
- return self._wave_id
-
- @property
- def creator(self):
- """Returns the participant id of the creator of this wavelet."""
- return self._creator
-
- @property
- def creation_time(self):
- """Returns the time that this wavelet was first created in milliseconds."""
- return self._creation_time
-
- @property
- def data_documents(self):
- """Returns the data documents for this wavelet based on key name."""
- return self._data_documents
-
- @property
- def domain(self):
- """Return the domain that wavelet belongs to."""
- p = self._wave_id.find('!')
- if p == -1:
- return None
- else:
- return self._wave_id[:p]
-
- @property
- def last_modified_time(self):
- """Returns the time that this wavelet was last modified in ms."""
- return self._last_modified_time
-
- @property
- def participants(self):
- """Returns a set of participants on this wavelet."""
- return self._participants
-
- @property
- def root_thread(self):
- """Returns the root thread of this wavelet."""
- return self._root_thread
-
- @property
- def tags(self):
- """Returns a list of tags for this wavelet."""
- return self._tags
-
- @property
- def raw_deltas(self):
- """If present, return the raw deltas for this wavelet."""
- return self._raw_deltas
-
- @property
- def raw_snapshot(self):
- """If present, return the raw snapshot for this wavelet."""
- return self._raw_snapshot
-
- def _get_title(self):
- return self._title
-
- def _set_title(self, title):
- title = util.force_unicode(title)
-
- if title.find('\n') != -1:
- raise errors.Error('Wavelet title should not contain a newline ' +
- 'character. Specified: ' + title)
-
- self._operation_queue.wavelet_set_title(self.wave_id, self.wavelet_id,
- title)
- self._title = title
-
- # Adjust the content of the root blip, if it is available in the context.
- if self._root_blip:
- content = '\n'
- splits = self._root_blip._content.split('\n', 2)
- if len(splits) == 3:
- content += splits[2]
- self._root_blip._content = '\n' + title + content
-
- #: Returns or sets the wavelet's title.
- title = property(_get_title, _set_title,
- doc='Get or set the title of the wavelet.')
-
- def _get_robot_address(self):
- return self._robot_address
-
- def _set_robot_address(self, address):
- if self._robot_address:
- raise errors.Error('robot address already set')
- self._robot_address = address
-
- robot_address = property(_get_robot_address, _set_robot_address,
- doc='Get or set the address of the current robot.')
-
- @property
- def root_blip(self):
- """Returns this wavelet's root blip."""
- return self._root_blip
-
- @property
- def blips(self):
- """Returns the blips for this wavelet."""
- return self._blips
-
- def get_operation_queue(self):
- """Returns the OperationQueue for this wavelet."""
- return self._operation_queue
-
- def serialize(self):
- """Return a dict of the wavelet properties."""
- return {'waveId': self._wave_id,
- 'waveletId': self._wavelet_id,
- 'creator': self._creator,
- 'creationTime': self._creation_time,
- 'dataDocuments': self._data_documents.serialize(),
- 'lastModifiedTime': self._last_modified_time,
- 'participants': self._participants.serialize(),
- 'title': self._title,
- 'blips': self._blips.serialize(),
- 'rootBlipId': self._root_blip_id,
- 'rootThread': self._root_thread.serialize()
- }
-
- def proxy_for(self, proxy_for_id):
- """Return a view on this wavelet that will proxy for the specified id.
-
- A shallow copy of the current wavelet is returned with the proxy_for_id
- set. Any modifications made to this copy will be done using the
- proxy_for_id, i.e. the robot+<proxy_for_id>@appspot.com address will
- be used.
-
- If the wavelet was retrieved using the Active Robot API, that is
- by fetch_wavelet, then the address of the robot must be added to the
- wavelet by setting wavelet.robot_address before calling proxy_for().
- """
- util.check_is_valid_proxy_for_id(proxy_for_id)
- self.add_proxying_participant(proxy_for_id)
- operation_queue = self.get_operation_queue().proxy_for(proxy_for_id)
- res = Wavelet(json={},
- blips={}, root_thread=None,
- operation_queue=operation_queue)
- res._wave_id = self._wave_id
- res._wavelet_id = self._wavelet_id
- res._creator = self._creator
- res._creation_time = self._creation_time
- res._data_documents = self._data_documents
- res._last_modified_time = self._last_modified_time
- res._participants = self._participants
- res._title = self._title
- res._raw_data = self._raw_data
- res._blips = self._blips
- res._root_blip = self._root_blip
- res._root_thread = self._root_thread
- return res
-
- def add_proxying_participant(self, id):
- """Ads a proxying participant to the wave.
-
- Proxying participants are of the form robot+proxy@domain.com. This
- convenience method constructs this id and then calls participants.add.
- """
- if not self.robot_address:
- raise errors.Error(
- 'Need a robot address to add a proxying for participant')
- robotid, domain = self.robot_address.split('@', 1)
- if '#' in robotid:
- robotid, version = robotid.split('#')
- else:
- version = None
- if '+' in robotid:
- newid = robotid.split('+', 1)[0] + '+' + id
- else:
- newid = robotid + '+' + id
- if version:
- newid += '#' + version
- newid += '@' + domain
- self.participants.add(newid)
-
- def submit_with(self, other_wavelet):
- """Submit this wavelet when the passed other wavelet is submited.
-
- wavelets constructed outside of the event callback need to
- be either explicitly submited using robot.submit(wavelet) or be
- associated with a different wavelet that will be submited or
- is part of the event callback.
- """
- other_wavelet._operation_queue.copy_operations(self._operation_queue)
- self._operation_queue = other_wavelet._operation_queue
-
- def reply(self, initial_content=None):
- """Replies to the conversation in this wavelet.
-
- Args:
- initial_content: If set, start with this (string) content.
-
- Returns:
- A transient version of the blip that contains the reply.
- """
- if not initial_content:
- initial_content = u'\n'
- initial_content = util.force_unicode(initial_content)
- blip_data = self._operation_queue.wavelet_append_blip(
- self.wave_id, self.wavelet_id, initial_content)
-
- instance = blip.Blip(blip_data, self._blips, self._operation_queue)
- self._blips._add(instance)
- self.root_blip.child_blip_ids.append(instance.blip_id)
- return instance
-
- def delete(self, todelete):
- """Remove a blip from this wavelet.
-
- Args:
- todelete: either a blip or a blip id to be removed.
- """
- if isinstance(todelete, blip.Blip):
- blip_id = todelete.blip_id
- else:
- blip_id = todelete
- self._operation_queue.blip_delete(self.wave_id, self.wavelet_id, blip_id)
- self._blips._remove_with_id(blip_id)
diff --git a/wave/src/main/java/python/api/wavelet_test.py b/wave/src/main/java/python/api/wavelet_test.py
deleted file mode 100644
index a238eb7..0000000
--- a/wave/src/main/java/python/api/wavelet_test.py
+++ /dev/null
@@ -1,199 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the wavelet module."""
-
-
-import unittest
-
-import blip
-import element
-import ops
-import wavelet
-
-import simplejson
-
-ROBOT_NAME = 'robot@appspot.com'
-
-TEST_WAVELET_DATA = {
- 'creator': ROBOT_NAME,
- 'creationTime': 100,
- 'lastModifiedTime': 101,
- 'participants': [ROBOT_NAME],
- 'participantsRoles': {ROBOT_NAME: wavelet.Participants.ROLE_FULL},
- 'rootBlipId': 'blip-1',
- 'title': 'Title',
- 'waveId': 'test.com!w+g3h3im',
- 'waveletId': 'test.com!root+conv',
- 'tags': ['tag1', 'tag2'],
- 'rootThread': {
- 'id': '',
- 'location': -1,
- 'blipIds': ['blip-1']
- }
-}
-
-TEST_BLIP_DATA = {
- 'blipId': TEST_WAVELET_DATA['rootBlipId'],
- 'childBlipIds': [],
- 'content': '\ntesting',
- 'contributors': [TEST_WAVELET_DATA['creator'], 'robot@google.com'],
- 'creator': TEST_WAVELET_DATA['creator'],
- 'lastModifiedTime': TEST_WAVELET_DATA['lastModifiedTime'],
- 'parentBlipId': None,
- 'waveId': TEST_WAVELET_DATA['waveId'],
- 'elements': {},
- 'waveletId': TEST_WAVELET_DATA['waveletId'],
- 'replyThreadIds': [],
- 'threadId': ''
-}
-
-
-class TestWavelet(unittest.TestCase):
- """Tests the wavelet class."""
-
- def setUp(self):
- self.operation_queue = ops.OperationQueue()
- self.all_blips = {}
- self.blip = blip.Blip(TEST_BLIP_DATA,
- self.all_blips,
- self.operation_queue)
- self.all_blips[self.blip.blip_id] = self.blip
- root_thread_data = TEST_WAVELET_DATA.get('rootThread')
- root_thread = wavelet.BlipThread('',
- root_thread_data.get('location'),
- root_thread_data.get('blipIds', []),
- self.all_blips,
- self.operation_queue)
-
- self.wavelet = wavelet.Wavelet(TEST_WAVELET_DATA,
- self.all_blips,
- root_thread,
- self.operation_queue)
- self.wavelet.robot_address = ROBOT_NAME
-
- def testWaveletProperties(self):
- w = self.wavelet
- self.assertEquals(TEST_WAVELET_DATA['creator'], w.creator)
- self.assertEquals(TEST_WAVELET_DATA['creationTime'], w.creation_time)
- self.assertEquals(TEST_WAVELET_DATA['lastModifiedTime'],
- w.last_modified_time)
- self.assertEquals(len(TEST_WAVELET_DATA['participants']),
- len(w.participants))
- self.assertTrue(TEST_WAVELET_DATA['participants'][0] in w.participants)
- self.assertEquals(TEST_WAVELET_DATA['rootBlipId'], w.root_blip.blip_id)
- self.assertEquals(TEST_WAVELET_DATA['title'], w.title)
- self.assertEquals(TEST_WAVELET_DATA['waveId'], w.wave_id)
- self.assertEquals(TEST_WAVELET_DATA['waveletId'], w.wavelet_id)
- self.assertEquals(TEST_WAVELET_DATA['rootThread']['id'], w.root_thread.id)
- self.assertEquals(TEST_WAVELET_DATA['rootThread']['location'],
- w.root_thread.location)
- self.assertEquals(len(TEST_WAVELET_DATA['rootThread']['blipIds']),
- len(w.root_thread.blips))
- self.assertEquals('test.com', w.domain)
-
- def testWaveletMethods(self):
- w = self.wavelet
- reply = w.reply()
- self.assertEquals(2, len(w.blips))
- w.delete(reply)
- self.assertEquals(1, len(w.blips))
- self.assertEquals(0, len(w.data_documents))
- self.wavelet.data_documents['key'] = 'value'
- self.assert_('key' in w.data_documents)
- self.assertEquals(1, len(w.data_documents))
- for key in w.data_documents:
- self.assertEquals(key, 'key')
- self.assertEquals(1, len(w.data_documents.keys()))
- self.wavelet.data_documents['key'] = None
- self.assertEquals(0, len(w.data_documents))
- num_participants = len(w.participants)
- w.proxy_for('proxy').reply()
- self.assertEquals(2, len(w.blips))
- # check that the new proxy for participant was added
- self.assertEquals(num_participants + 1, len(w.participants))
- w._robot_address = ROBOT_NAME.replace('@', '+proxy@')
- w.proxy_for('proxy').reply()
- self.assertEquals(num_participants + 1, len(w.participants))
- self.assertEquals(3, len(w.blips))
-
- def testSetTitle(self):
- self.blip._content = '\nOld title\n\nContent'
- self.wavelet.title = 'New title \xd0\xb0\xd0\xb1\xd0\xb2'
- self.assertEquals(1, len(self.operation_queue))
- self.assertEquals('wavelet.setTitle',
- self.operation_queue.serialize()[1]['method'])
- self.assertEquals(u'\nNew title \u0430\u0431\u0432\n\nContent',
- self.blip._content)
-
- def testSetTitleAdjustRootBlipWithOneLineProperly(self):
- self.blip._content = '\nOld title'
- self.wavelet.title = 'New title'
- self.assertEquals(1, len(self.operation_queue))
- self.assertEquals('wavelet.setTitle',
- self.operation_queue.serialize()[1]['method'])
- self.assertEquals('\nNew title\n', self.blip._content)
-
- def testSetTitleAdjustEmptyRootBlipProperly(self):
- self.blip._content = '\n'
- self.wavelet.title = 'New title'
- self.assertEquals(1, len(self.operation_queue))
- self.assertEquals('wavelet.setTitle',
- self.operation_queue.serialize()[1]['method'])
- self.assertEquals('\nNew title\n', self.blip._content)
-
- def testTags(self):
- w = self.wavelet
- self.assertEquals(2, len(w.tags))
- w.tags.append('tag3')
- self.assertEquals(3, len(w.tags))
- w.tags.append('tag3')
- self.assertEquals(3, len(w.tags))
- w.tags.remove('tag1')
- self.assertEquals(2, len(w.tags))
- self.assertEquals('tag2', w.tags[0])
-
- def testParticipantRoles(self):
- w = self.wavelet
- self.assertEquals(wavelet.Participants.ROLE_FULL,
- w.participants.get_role(ROBOT_NAME))
- w.participants.set_role(ROBOT_NAME, wavelet.Participants.ROLE_READ_ONLY)
- self.assertEquals(wavelet.Participants.ROLE_READ_ONLY,
- w.participants.get_role(ROBOT_NAME))
-
- def testSerialize(self):
- self.blip.append(element.Gadget('http://test.com', {'a': 3}))
- self.wavelet.title = 'A wavelet title'
- self.blip.append(element.Image(url='http://www.google.com/logos/clickortreat1.gif',
- width=320, height=118))
- self.blip.append(element.Attachment(caption='fake', data='fake data'))
- self.blip.append(element.Line(line_type='li', indent='2'))
- self.blip.append('bulleted!')
- self.blip.append(element.Installer(
- 'http://wave-skynet.appspot.com/public/extensions/areyouin/manifest.xml'))
- self.wavelet.proxy_for('proxy').reply().append('hi from douwe')
- inlineBlip = self.blip.insert_inline_blip(5)
- inlineBlip.append('hello again!')
-
- serialized = self.wavelet.serialize()
- serialized = simplejson.dumps(serialized)
- self.assertTrue(serialized.find('test.com') > 0)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/wave/src/main/java/python/api/waveservice.py b/wave/src/main/java/python/api/waveservice.py
deleted file mode 100644
index 444a26b..0000000
--- a/wave/src/main/java/python/api/waveservice.py
+++ /dev/null
@@ -1,468 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Base class to use OAuth to talk to the wave service."""
-
-import httplib
-import logging
-import urllib
-import urlparse
-
-import oauth
-import simplejson
-
-import ops
-import blip
-import errors
-import events
-import search
-import util
-import wavelet
-
-
-class WaveService(object):
- # Google OAuth URLs
- REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken'
- ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken'
- AUTHORIZATION_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken'
-
- # Wave OAuth URLS
- SCOPE = 'http://wave.googleusercontent.com/api/rpc'
- SIGNATURE_METHOD = oauth.OAuthSignatureMethod_HMAC_SHA1()
-
- # Wave RPC URLs
- RPC_URL = 'https://www-opensocial.googleusercontent.com/api/rpc'
- SANDBOX_RPC_URL = (
- 'https://www-opensocial-sandbox.googleusercontent.com/api/rpc')
-
- def __init__(self, use_sandbox=False, server_rpc_base=None,
- consumer_key='anonymous', consumer_secret='anonymous',
- http_post=None):
- """Initializes a service that can perform the various OAuth steps.
-
- Args:
- use_sandbox: A boolean indicating whether to use Wave Sandbox URLs
- server_rpc_base: optional explicit url to use for rpc,
- overriding use_sandbox.
- consumer_key: A string for the consumer key, defaults to 'anonymous'
- consumer_secret: A string for the consumer secret, defaults to 'anonymous'
- http_post: handler to call to execute a http post.
- """
- self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
- logging.info('server_rpc_base: %s', server_rpc_base)
- if server_rpc_base:
- self._server_rpc_base = server_rpc_base
- elif use_sandbox:
- self._server_rpc_base = WaveService.SANDBOX_RPC_URL
- else:
- self._server_rpc_base = WaveService.RPC_URL
- logging.info('server:' + self._server_rpc_base)
-
- self._http_post = self.http_post
- self._connection = httplib.HTTPSConnection('www.google.com')
- self._access_token = None
-
- def _make_token(self, token):
- """If passed an oauth token, return that. If passed a string, convert."""
- if isinstance(token, basestring):
- return oauth.OAuthToken.from_string(token)
- else:
- return token
-
- def set_http_post(self, http_post):
- """Set the http_post handler to use when posting."""
- self._http_post = http_post
-
- def get_token_from_request(self, oauth_request):
- """Convenience function to returning the token from a request.
-
- Args:
- oauth_request: An OAuthRequest object
- Returns:
- An OAuthToken object
- """
- # Send request to the request token URL
- self._connection.request(oauth_request.http_method, oauth_request.to_url())
-
- # Extract token from response
- response = self._connection.getresponse().read()
- self._request_token = oauth.OAuthToken.from_string(response)
- return self._request_token
-
- def fetch_request_token(self, callback=None):
- """Fetches the request token to start the oauth dance.
-
- Args:
- callback: the URL to where the service will redirect to after
- access is granted.
- Returns:
- An OAuthToken object
- """
- # Create and sign OAuth request
- params = {'scope': WaveService.SCOPE}
- if callback:
- params['oauth_callback'] = callback
- oauth_request = oauth.OAuthRequest.from_consumer_and_token(self._consumer,
- http_url=WaveService.REQUEST_TOKEN_URL, parameters=params)
- oauth_request.sign_request(WaveService.SIGNATURE_METHOD, self._consumer, None)
-
- return self.get_token_from_request(oauth_request)
-
- def generate_authorization_url(self, request_token=None):
- """Generates the authorization URL (Step 2).
-
- Args:
- request_token: An OAuthToken object
- Returns:
- An authorization URL
- """
- # Create Authorization URL request
- if request_token is None:
- request_token = self._request_token
- oauth_request = oauth.OAuthRequest.from_token_and_callback(
- token=request_token, http_url=WaveService.AUTHORIZATION_URL)
-
- # Send request
- self._connection.request(oauth_request.http_method, oauth_request.to_url())
-
- # Extract location from the response
- response = self._connection.getresponse()
- return response.getheader('location')
-
- def upgrade_to_access_token(self, request_token, verifier=None):
- """Upgrades the request_token to an access token (Step 3).
-
- Args:
- request_token: An OAuthToken object or string
- verifier: A verifier string
- Returns:
- An OAuthToken object
- """
- request_token = self._make_token(request_token)
- params = {}
- if verifier:
- params['oauth_verifier'] = verifier
- oauth_request = oauth.OAuthRequest.from_consumer_and_token(self._consumer,
- token=request_token, http_url=WaveService.ACCESS_TOKEN_URL,
- parameters=params)
- oauth_request.sign_request(WaveService.SIGNATURE_METHOD, self._consumer,
- request_token)
-
- self._access_token = self.get_token_from_request(oauth_request)
- return self._access_token
-
- def set_access_token(self, access_token):
- self._access_token = self._make_token(access_token)
-
- def http_post(self, url, data, headers):
- """Execute an http post.
-
- You can provide a different method to use in the constructor. This
- is mostly useful when running on app engine and you want to set
- the time out to something different than the default 5 seconds.
-
- Args:
- url: to post to
- body: post body
- headers: extra headers to pass along
- Returns:
- response_code, returned_page
- """
- import urllib2
- req = urllib2.Request(url,
- data=data,
- headers=headers)
- try:
- f = urllib2.urlopen(req)
- return f.code, f.read()
- except urllib2.HTTPError, e:
- return e.code, e.read()
-
- def make_rpc(self, operations):
- """Make an rpc call, submitting the specified operations."""
-
- rpc_host = urlparse.urlparse(self._server_rpc_base).netloc
-
- # We either expect an operationqueue, a single op or a list
- # of ops:
- if (not isinstance(operations, ops.OperationQueue)):
- if not isinstance(operations, list):
- operations = [operations]
- queue = ops.OperationQueue()
- queue.copy_operations(operations)
- else:
- queue = operations
-
-
- data = simplejson.dumps(queue.serialize(method_prefix='wave'))
-
- oauth_request = oauth.OAuthRequest.from_consumer_and_token(self._consumer,
- token=self._access_token, http_method='POST',
- http_url=self._server_rpc_base)
- oauth_request.sign_request(WaveService.SIGNATURE_METHOD,
- self._consumer, self._access_token)
-
- logging.info('Active URL: %s' % self._server_rpc_base)
- logging.info('Active Outgoing: %s' % data)
- headers = {'Content-Type': 'application/json'}
- headers.update(oauth_request.to_header());
- status, content = self._http_post(
- url=self._server_rpc_base,
- data=data,
- headers=headers)
-
- if status != 200:
- raise errors.RpcError('code: %s\n%s' % (status, content))
- return simplejson.loads(content)
-
- def _first_rpc_result(self, result):
- """result is returned from make_rpc. Get the first data record
- or throw an exception if it was an error. Ignore responses to
- NOTIFY_OP_ID."""
- result = [record for record in result if record['id'] != ops.NOTIFY_OP_ID]
- if not result:
- raise errors.RpcError('No results found.')
- result = result[0]
- error = result.get('error')
- if error:
- raise errors.RpcError(str(error['code'])
- + ': ' + error['message'])
- data = result.get('data')
- if data is not None:
- return data
- raise errors.Error('RPC Error: No data record.')
-
- def _wavelet_from_json(self, json, pending_ops):
- """Construct a wavelet from the passed json.
-
- The json should either contain a wavelet and a blips record that
- define those respective object. The returned wavelet
- will be constructed using the passed pending_ops
- OperationQueue.
- Alternatively the json can be the result of a previous
- wavelet.serialize() call. In that case the blips will
- be contaned in the wavelet record.
- """
- if isinstance(json, basestring):
- json = simplejson.loads(json)
-
- # Create blips dict so we can pass into BlipThread objects
- blips = {}
-
- # Setup threads first, as the Blips and Wavelet need to know about them
- threads = {}
- # In case of blind_wavelet or new_wave, we may not have threads indo
- threads_data = json.get('threads', {})
- # Create remaining thread objects
- for thread_id, raw_thread_data in threads_data.items():
- threads[thread_id] = wavelet.BlipThread(thread_id,
- raw_thread_data.get('location'), raw_thread_data.get('blipIds', []),
- blips, pending_ops)
-
- # If being called from blind_wavelet, wavelet is top level info
- if 'wavelet' in json:
- raw_wavelet_data = json['wavelet']
- elif 'waveletData' in json:
- raw_wavelet_data = json['waveletData']
- else:
- raw_wavelet_data = json
- root_thread_data = raw_wavelet_data.get('rootThread')
- root_thread = wavelet.BlipThread('',
- root_thread_data.get('location'),
- root_thread_data.get('blipIds', []),
- blips,
- pending_ops)
- threads[''] = root_thread
-
- # Setup the blips, pass in reply threads
- for blip_id, raw_blip_data in json['blips'].items():
- reply_threads = [threads[id] for id in raw_blip_data.get('replyThreadIds',
- [])]
- thread = threads.get(raw_blip_data.get('threadId'))
- blips[blip_id] = blip.Blip(raw_blip_data, blips, pending_ops,
- thread=thread, reply_threads=reply_threads)
-
- result = wavelet.Wavelet(raw_wavelet_data, blips, root_thread, pending_ops,
- raw_deltas=json.get('rawDeltas'))
-
- robot_address = json.get('robotAddress')
- if robot_address:
- result.robot_address = robot_address
-
- return result
-
- def search(self, query, index=None, num_results=None):
- """Execute a search request.
-
- Args:
- query: what to search for, for example [in:inbox]
- index: index of the first result to return
- num_results: how many results to return
- """
- operation_queue = ops.OperationQueue()
- operation_queue.robot_search(query, index, num_results)
- result = self._first_rpc_result(self.make_rpc(operation_queue))
- return search.Results(result)
-
- def new_wave(self, domain, participants=None, message='', proxy_for_id=None,
- submit=False):
- """Create a new wave with the initial participants on it.
-
- A new wave is returned with its own operation queue. It the
- responsibility of the caller to make sure this wave gets
- submitted to the server, either by calling robot.submit() or
- by calling .submit_with() on the returned wave.
-
- Args:
- domain: the domain to create the wavelet on. This should
- in general correspond to the domain of the incoming
- wavelet. (wavelet.domain). Exceptions are situations
- where the robot is calling new_wave outside of an
- event or when the server is handling multiple domains.
-
- participants: initial participants on the wave. The robot
- as the creator of the wave is always added.
-
- message: a string that will be passed back to the robot
- when the WAVELET_CREATOR event is fired. This is a
- lightweight way to pass around state.
-
- submit: if true, use the active gateway to make a round
- trip to the server. This will return immediately an
- actual waveid/waveletid and blipId for the root blip.
-
- """
- util.check_is_valid_proxy_for_id(proxy_for_id)
- operation_queue = ops.OperationQueue(proxy_for_id)
- if not isinstance(message, basestring):
- message = simplejson.dumps(message)
-
- # Create temporary wavelet data
- blip_data, wavelet_data = operation_queue.robot_create_wavelet(
- domain=domain,
- participants=participants,
- message=message)
-
- # Create temporary blips dictionary
- blips = {}
- root_blip = blip.Blip(blip_data, blips, operation_queue)
- blips[root_blip.blip_id] = root_blip
-
- if submit:
- # Submit operation to server and return actual wave/blip IDs
- temp_wavelet = wavelet.Wavelet(wavelet_data,
- blips=blips,
- root_thread=None,
- operation_queue=operation_queue)
- result = self._first_rpc_result(self.submit(temp_wavelet))
- if isinstance(result, list):
- result = result[0]
- if 'blipId' in result:
- blip_data['blipId'] = result['blipId']
- wavelet_data['rootBlipId'] = result['blipId']
- for field in 'waveId', 'waveletId':
- if field in result:
- wavelet_data[field] = result[field]
- blip_data[field] = result[field]
- blips = {}
- root_blip = blip.Blip(blip_data, blips, operation_queue)
- blips[root_blip.blip_id] = root_blip
-
- root_thread = wavelet.BlipThread('',
- -1,
- [root_blip.blip_id],
- blips,
- operation_queue)
- new_wavelet = wavelet.Wavelet(wavelet_data,
- blips=blips,
- root_thread=root_thread,
- operation_queue=operation_queue)
- return new_wavelet
-
- def fetch_wavelet(self, wave_id, wavelet_id=None, proxy_for_id=None,
- raw_deltas_from_version=-1, return_raw_snapshot=False):
- """Use the REST interface to fetch a wave and return it.
-
- The returned wavelet contains a snapshot of the state of the
- wavelet at that point. It can be used to modify the wavelet,
- but the wavelet might change in between, so treat carefully.
-
- Also note that the wavelet returned has its own operation
- queue. It the responsibility of the caller to make sure this
- wavelet gets submited to the server, either by calling
- robot.submit() or by calling .submit_with() on the returned
- wavelet.
-
- Args:
- wave_id: the wave id
- wavelet_id: the wavelet_id
- proxy_for_id: on whose behalf to execute the operation
- raw_deltas_from_version: If specified, return a raw dump of the
- delta history of this wavelet, starting at the given version.
- This may return only part of the history; use additional
- requests with higher raw_deltas_from_version parameters to
- get the rest.
- return_raw_snapshot: if true, return the raw data for this
- wavelet.
- """
- util.check_is_valid_proxy_for_id(proxy_for_id)
- if not wavelet_id:
- domain, id = wave_id.split('!', 1)
- wavelet_id = domain + '!conv+root'
- operation_queue = ops.OperationQueue(proxy_for_id)
- operation_queue.robot_fetch_wave(wave_id, wavelet_id,
- raw_deltas_from_version, return_raw_snapshot)
- result = self._first_rpc_result(self.make_rpc(operation_queue))
- return self._wavelet_from_json(result, ops.OperationQueue(proxy_for_id))
-
- def blind_wavelet(self, json, proxy_for_id=None):
- """Construct a blind wave from a json string.
-
- Call this method if you have a snapshot of a wave that you
- want to operate on outside of an event. Since the wave might
- have changed since you last saw it, you should take care to
- submit operations that are as safe as possible.
-
- Args:
- json: a json object or string containing at least a key
- wavelet defining the wavelet and a key blips defining the
- blips in the view.
-
- proxy_for_id: the proxying information that will be set on the wavelet's
- operation queue.
-
- Returns:
- A new wavelet with its own operation queue. It the
- responsibility of the caller to make sure this wavelet gets
- submited to the server, either by calling robot.submit() or
- by calling .submit_with() on the returned wavelet.
- """
- util.check_is_valid_proxy_for_id(proxy_for_id)
- return self._wavelet_from_json(json, ops.OperationQueue(proxy_for_id))
-
- def submit(self, wavelet_to_submit):
- """Submit the pending operations associated with wavelet_to_submit.
-
- Typically the wavelet will be the result of fetch_wavelet, blind_wavelet
- or new_wave.
- """
- pending = wavelet_to_submit.get_operation_queue()
- res = self.make_rpc(pending)
- pending.clear()
- return res
diff --git a/wave/src/main/java/python/api/waveservice_test.py b/wave/src/main/java/python/api/waveservice_test.py
deleted file mode 100644
index da433da..0000000
--- a/wave/src/main/java/python/api/waveservice_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/python2.4
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Unit tests for the wavelet module."""
-
-
-import unittest
-
-import blip
-import element
-import ops
-import wavelet
-import waveservice
-import simplejson
-import testdata
-
-
-class TestWavelet(unittest.TestCase):
- """Tests the wavelet class."""
-
- def setUp(self):
- self.waveservice = waveservice.WaveService()
-
- def testWaveletProperties(self):
- operation_queue = ops.OperationQueue()
- TEST_DATA = simplejson.loads(testdata.json_string)
- w = self.waveservice._wavelet_from_json(TEST_DATA,
- operation_queue)
- self.assertEquals(TEST_DATA['wavelet']['waveId'], w.wave_id)
- self.assertEquals(TEST_DATA['wavelet']['rootThread']['id'],
- w.root_thread.id)
- self.assertEquals(TEST_DATA['wavelet']['rootThread']['location'],
- w.root_thread.location)
- self.assertEquals(len(TEST_DATA['wavelet']['rootThread']['blipIds']),
- len(w.root_thread.blips))
-
- b = w.root_blip
- self.assertEquals(len(TEST_DATA['blips']['b+IvD7RCuWB']['replyThreadIds']),
- len(b.reply_threads))
-
-
- def testWaveletBlipMethods(self):
- operation_queue = ops.OperationQueue()
- TEST_DATA = simplejson.loads(testdata.json_string)
- w = self.waveservice._wavelet_from_json(TEST_DATA,
- operation_queue)
- root_blip = w.root_blip
- blip = root_blip.continue_thread()
- self.assertEquals(blip.parent_blip_id, root_blip.parent_blip_id)
- self.assertEquals(8, len(w.blips))
- self.assertEquals(4, len(w.root_thread.blips))
-
-
-
-if __name__ == '__main__':
- unittest.main()