App Engine Python SDK version 1.9.2
[gae.git] / python / google / appengine / api / backendinfo.py
blob935e465311964c9e298446b3208bd017669b1f24
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 """
22 A library for working with BackendInfoExternal records, describing backends
23 configured for an application. Supports loading the records from backend.yaml.
24 """
32 import os
33 import yaml
34 from yaml import representer
36 if os.environ.get('APPENGINE_RUNTIME') == 'python27':
37 from google.appengine.api import validation
38 from google.appengine.api import yaml_builder
39 from google.appengine.api import yaml_listener
40 from google.appengine.api import yaml_object
41 else:
42 from google.appengine.api import validation
43 from google.appengine.api import yaml_builder
44 from google.appengine.api import yaml_listener
45 from google.appengine.api import yaml_object
47 NAME_REGEX = r'(?!-)[a-z\d\-]{1,100}'
48 FILE_REGEX = r'(?!\^).*(?!\$).{1,256}'
49 CLASS_REGEX = r'^[bB](1|2|4|8|4_1G)$'
50 OPTIONS_REGEX = r'^[a-z, ]*$'
51 STATE_REGEX = r'^(START|STOP|DISABLED)$'
54 BACKENDS = 'backends'
57 NAME = 'name'
58 CLASS = 'class'
59 INSTANCES = 'instances'
60 OPTIONS = 'options'
61 PUBLIC = 'public'
62 DYNAMIC = 'dynamic'
63 FAILFAST = 'failfast'
64 MAX_CONCURRENT_REQUESTS = 'max_concurrent_requests'
65 START = 'start'
67 VALID_OPTIONS = frozenset([PUBLIC, DYNAMIC, FAILFAST])
70 STATE = 'state'
72 class BadConfig(Exception):
73 """An invalid configuration was provided."""
75 class ListWithoutSort(list):
76 def sort(self):
77 pass
79 class SortedDict(dict):
80 def __init__(self, keys, data):
81 super(SortedDict, self).__init__()
82 self.keys = keys
83 self.update(data)
85 def items(self):
86 result = ListWithoutSort()
87 for key in self.keys:
88 if type(self.get(key)) != type(None):
89 result.append((key, self.get(key)))
90 return result
92 representer.SafeRepresenter.add_representer(
93 SortedDict, representer.SafeRepresenter.represent_dict)
95 class BackendEntry(validation.Validated):
96 """A backend entry describes a single backend."""
97 ATTRIBUTES = {
98 NAME: NAME_REGEX,
99 CLASS: validation.Optional(CLASS_REGEX),
100 INSTANCES: validation.Optional(validation.TYPE_INT),
101 MAX_CONCURRENT_REQUESTS: validation.Optional(validation.TYPE_INT),
104 OPTIONS: validation.Optional(OPTIONS_REGEX),
105 PUBLIC: validation.Optional(validation.TYPE_BOOL),
106 DYNAMIC: validation.Optional(validation.TYPE_BOOL),
107 FAILFAST: validation.Optional(validation.TYPE_BOOL),
108 START: validation.Optional(FILE_REGEX),
111 STATE: validation.Optional(STATE_REGEX),
114 def __init__(self, *args, **kwargs):
115 super(BackendEntry, self).__init__(*args, **kwargs)
116 self.Init()
118 def Init(self):
119 if self.public:
120 raise BadConfig("Illegal field: 'public'")
121 if self.dynamic:
122 raise BadConfig("Illegal field: 'dynamic'")
123 if self.failfast:
124 raise BadConfig("Illegal field: 'failfast'")
125 self.ParseOptions()
126 return self
128 def set_class(self, Class):
129 """Setter for 'class', since an attribute reference is an error."""
130 self.Set(CLASS, Class)
132 def get_class(self):
133 """Accessor for 'class', since an attribute reference is an error."""
134 return self.Get(CLASS)
136 def ToDict(self):
137 """Returns a sorted dictionary representing the backend entry."""
138 self.ParseOptions().WriteOptions()
139 result = super(BackendEntry, self).ToDict()
140 return SortedDict([NAME,
141 CLASS,
142 INSTANCES,
143 START,
144 OPTIONS,
145 MAX_CONCURRENT_REQUESTS,
146 STATE],
147 result)
149 def ParseOptions(self):
150 """Parses the 'options' field and sets appropriate fields."""
151 if self.options:
152 options = [option.strip() for option in self.options.split(',')]
153 else:
154 options = []
156 for option in options:
157 if option not in VALID_OPTIONS:
158 raise BadConfig('Unrecognized option: %s', option)
160 self.public = PUBLIC in options
161 self.dynamic = DYNAMIC in options
162 self.failfast = FAILFAST in options
163 return self
165 def WriteOptions(self):
166 """Writes the 'options' field based on other settings."""
167 options = []
168 if self.public:
169 options.append('public')
170 if self.dynamic:
171 options.append('dynamic')
172 if self.failfast:
173 options.append('failfast')
174 if options:
175 self.options = ', '.join(options)
176 else:
177 self.options = None
178 return self
181 def LoadBackendEntry(backend_entry):
182 """Parses a BackendEntry object from a string.
184 Args:
185 backend_entry: a backend entry, as a string
187 Returns:
188 A BackendEntry object.
190 builder = yaml_object.ObjectBuilder(BackendEntry)
191 handler = yaml_builder.BuilderHandler(builder)
192 listener = yaml_listener.EventListener(handler)
193 listener.Parse(backend_entry)
195 entries = handler.GetResults()
196 if len(entries) < 1:
197 raise BadConfig('Empty backend configuration.')
198 if len(entries) > 1:
199 raise BadConfig('Multiple backend entries were found in configuration.')
201 return entries[0].Init()
204 class BackendInfoExternal(validation.Validated):
205 """BackendInfoExternal describes all backend entries for an application."""
206 ATTRIBUTES = {
207 BACKENDS: validation.Optional(validation.Repeated(BackendEntry)),
211 def LoadBackendInfo(backend_info, open_fn=None):
212 """Parses a BackendInfoExternal object from a string.
214 Args:
215 backend_info: a backends stanza (list of backends) as a string
216 open_fn: Function for opening files. Unused.
218 Returns:
219 A BackendInfoExternal object.
221 builder = yaml_object.ObjectBuilder(BackendInfoExternal)
222 handler = yaml_builder.BuilderHandler(builder)
223 listener = yaml_listener.EventListener(handler)
224 listener.Parse(backend_info)
226 backend_info = handler.GetResults()
227 if len(backend_info) < 1:
228 return BackendInfoExternal(backends=[])
229 if len(backend_info) > 1:
230 raise BadConfig("Only one 'backends' clause is allowed.")
232 info = backend_info[0]
233 if not info.backends:
234 return BackendInfoExternal(backends=[])
236 for backend in info.backends:
237 backend.Init()
238 return info