summaryrefslogtreecommitdiffstats
path: root/google_appengine/google/appengine/api/yaml_listener.py
blob: e7d978fbcfa3dad8989ecb2a23dcade039cb6878 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed 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.
#

"""PyYAML event listener

Contains class which interprets YAML events and forwards them to
a handler object.
"""


from google.appengine.api import yaml_errors
import yaml


_EVENT_METHOD_MAP = {
  yaml.events.StreamStartEvent: 'StreamStart',
  yaml.events.StreamEndEvent: 'StreamEnd',
  yaml.events.DocumentStartEvent: 'DocumentStart',
  yaml.events.DocumentEndEvent: 'DocumentEnd',
  yaml.events.AliasEvent: 'Alias',
  yaml.events.ScalarEvent: 'Scalar',
  yaml.events.SequenceStartEvent: 'SequenceStart',
  yaml.events.SequenceEndEvent: 'SequenceEnd',
  yaml.events.MappingStartEvent: 'MappingStart',
  yaml.events.MappingEndEvent: 'MappingEnd',
}


class EventHandler(object):
  """Handler interface for parsing YAML files.

  Implement this interface to define specific YAML event handling class.
  Implementing classes instances are passed to the constructor of
  EventListener to act as a receiver of YAML parse events.
  """
  def StreamStart(self, event, loader):
    """Handle start of stream event"""

  def StreamEnd(self, event, loader):
    """Handle end of stream event"""

  def DocumentStart(self, event, loader):
    """Handle start of document event"""

  def DocumentEnd(self, event, loader):
    """Handle end of document event"""

  def Alias(self, event, loader):
    """Handle alias event"""

  def Scalar(self, event, loader):
    """Handle scalar event"""

  def SequenceStart(self, event, loader):
    """Handle start of sequence event"""

  def SequenceEnd(self, event, loader):
    """Handle end of sequence event"""

  def MappingStart(self, event, loader):
    """Handle start of mappping event"""

  def MappingEnd(self, event, loader):
    """Handle end of mapping event"""


class EventListener(object):
  """Helper class to re-map PyYAML events to method calls.

  By default, PyYAML generates its events via a Python generator.  This class
  is a helper that iterates over the events from the PyYAML parser and forwards
  them to a handle class in the form of method calls.  For simplicity, the
  underlying event is forwarded to the handler as a parameter to the call.

  This object does not itself produce iterable objects, but is really a mapping
  to a given handler instance.

    Example use:

      class PrintDocumentHandler(object):
        def DocumentStart(event):
          print "A new document has been started"

      EventListener(PrintDocumentHandler()).Parse('''
        key1: value1
        ---
        key2: value2
        '''

      >>> A new document has been started
          A new document has been started

  In the example above, the implemented handler class (PrintDocumentHandler)
  has a single method which reports each time a new document is started within
  a YAML file.  It is not necessary to subclass the EventListener, merely it
  receives a PrintDocumentHandler instance.  Every time a new document begins,
  PrintDocumentHandler.DocumentStart is called with the PyYAML event passed
  in as its parameter..
  """

  def __init__(self, event_handler):
    """Initialize PyYAML event listener.

    Constructs internal mapping directly from event type to method on actual
    handler.  This prevents reflection being used during actual parse time.

    Args:
      event_handler: Event handler that will receive mapped events. Must
        implement at least one appropriate handler method named from
        the values of the _EVENT_METHOD_MAP.

    Raises:
      ListenerConfigurationError if event_handler is not an EventHandler.
    """
    if not isinstance(event_handler, EventHandler):
      raise yaml_errors.ListenerConfigurationError(
        'Must provide event handler of type yaml_listener.EventHandler')
    self._event_method_map = {}
    for event, method in _EVENT_METHOD_MAP.iteritems():
      self._event_method_map[event] = getattr(event_handler, method)

  def HandleEvent(self, event, loader=None):
    """Handle individual PyYAML event.

    Args:
      event: Event to forward to method call in method call.

    Raises:
      IllegalEvent when receives an unrecognized or unsupported event type.
    """
    if event.__class__ not in _EVENT_METHOD_MAP:
      raise yaml_errors.IllegalEvent(
            "%s is not a valid PyYAML class" % event.__class__.__name__)
    if event.__class__ in self._event_method_map:
      self._event_method_map[event.__class__](event, loader)

  def _HandleEvents(self, events):
    """Iterate over all events and send them to handler.

    This method is not meant to be called from the interface.

    Only use in tests.

    Args:
      events: Iterator or generator containing events to process.
    raises:
      EventListenerParserError when a yaml.parser.ParserError is raised.
      EventError when an exception occurs during the handling of an event.
    """
    for event in events:
      try:
        self.HandleEvent(*event)
      except Exception, e:
        event_object, loader = event
        raise yaml_errors.EventError(e, event_object)

  def _GenerateEventParameters(self,
                               stream,
                               loader_class=yaml.loader.SafeLoader):
    """Creates a generator that yields event, loader parameter pairs.

    For use as parameters to HandleEvent method for use by Parse method.
    During testing, _GenerateEventParameters is simulated by allowing
    the harness to pass in a list of pairs as the parameter.

    A list of (event, loader) pairs must be passed to _HandleEvents otherwise
    it is not possible to pass the loader instance to the handler.

    Also responsible for instantiating the loader from the Loader
    parameter.

    Args:
      stream: String document or open file object to process as per the
        yaml.parse method.  Any object that implements a 'read()' method which
        returns a string document will work.
      Loader: Loader class to use as per the yaml.parse method.  Used to
        instantiate new yaml.loader instance.

    Yields:
      Tuple(event, loader) where:
        event: Event emitted by PyYAML loader.
        loader_class: Used for dependency injection.
    """
    assert loader_class is not None
    try:
      loader = loader_class(stream)
      while loader.check_event():
        yield (loader.get_event(), loader)
    except yaml.error.YAMLError, e:
      raise yaml_errors.EventListenerYAMLError(e)

  def Parse(self, stream, loader_class=yaml.loader.SafeLoader):
    """Call YAML parser to generate and handle all events.

    Calls PyYAML parser and sends resulting generator to handle_event method
    for processing.

    Args:
      stream: String document or open file object to process as per the
        yaml.parse method.  Any object that implements a 'read()' method which
        returns a string document will work with the YAML parser.
      loader_class: Used for dependency injection.
    """
    self._HandleEvents(self._GenerateEventParameters(stream, loader_class))