1.9.30 sync.
[gae.git] / python / google / appengine / api / backendinfo.py
blob285bf9dd80113c6bebc23c103cfa06622d04f9f5
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 """
33 import os
34 import yaml
35 from yaml import representer
37 if os.environ.get('APPENGINE_RUNTIME') == 'python27':
38 from google.appengine.api import validation
39 from google.appengine.api import yaml_builder
40 from google.appengine.api import yaml_listener
41 from google.appengine.api import yaml_object
42 else:
43 from google.appengine.api import validation
44 from google.appengine.api import yaml_builder
45 from google.appengine.api import yaml_listener
46 from google.appengine.api import yaml_object
48 NAME_REGEX = r'(?!-)[a-z\d\-]{1,100}'
49 FILE_REGEX = r'(?!\^).*(?!\$).{1,256}'
50 CLASS_REGEX = r'^[bB](1|2|4|8|4_1G)$'
51 OPTIONS_REGEX = r'^[a-z, ]*$'
52 STATE_REGEX = r'^(START|STOP|DISABLED)$'
55 BACKENDS = 'backends'
58 NAME = 'name'
59 CLASS = 'class'
60 INSTANCES = 'instances'
61 OPTIONS = 'options'
62 PUBLIC = 'public'
63 DYNAMIC = 'dynamic'
64 FAILFAST = 'failfast'
65 MAX_CONCURRENT_REQUESTS = 'max_concurrent_requests'
66 START = 'start'
68 VALID_OPTIONS = frozenset([PUBLIC, DYNAMIC, FAILFAST])
71 STATE = 'state'
73 class BadConfig(Exception):
74 """An invalid configuration was provided."""
76 class ListWithoutSort(list):
77 def sort(self):
78 pass
80 class SortedDict(dict):
81 def __init__(self, keys, data):
82 super(SortedDict, self).__init__()
83 self.keys = keys
84 self.update(data)
86 def items(self):
87 result = ListWithoutSort()
88 for key in self.keys:
89 if type(self.get(key)) != type(None):
90 result.append((key, self.get(key)))
91 return result
93 representer.SafeRepresenter.add_representer(
94 SortedDict, representer.SafeRepresenter.represent_dict)
96 class BackendEntry(validation.Validated):
97 """A backend entry describes a single backend."""
98 ATTRIBUTES = {
99 NAME: NAME_REGEX,
100 CLASS: validation.Optional(CLASS_REGEX),
101 INSTANCES: validation.Optional(validation.TYPE_INT),
102 MAX_CONCURRENT_REQUESTS: validation.Optional(validation.TYPE_INT),
105 OPTIONS: validation.Optional(OPTIONS_REGEX),
106 PUBLIC: validation.Optional(validation.TYPE_BOOL),
107 DYNAMIC: validation.Optional(validation.TYPE_BOOL),
108 FAILFAST: validation.Optional(validation.TYPE_BOOL),
109 START: validation.Optional(FILE_REGEX),
112 STATE: validation.Optional(STATE_REGEX),
115 def __init__(self, *args, **kwargs):
116 super(BackendEntry, self).__init__(*args, **kwargs)
117 self.Init()
119 def Init(self):
120 if self.public:
121 raise BadConfig("Illegal field: 'public'")
122 if self.dynamic:
123 raise BadConfig("Illegal field: 'dynamic'")
124 if self.failfast:
125 raise BadConfig("Illegal field: 'failfast'")
126 self.ParseOptions()
127 return self
129 def set_class(self, Class):
130 """Setter for 'class', since an attribute reference is an error."""
131 self.Set(CLASS, Class)
133 def get_class(self):
134 """Accessor for 'class', since an attribute reference is an error."""
135 return self.Get(CLASS)
137 def ToDict(self):
138 """Returns a sorted dictionary representing the backend entry."""
139 self.ParseOptions().WriteOptions()
140 result = super(BackendEntry, self).ToDict()
141 return SortedDict([NAME,
142 CLASS,
143 INSTANCES,
144 START,
145 OPTIONS,
146 MAX_CONCURRENT_REQUESTS,
147 STATE],
148 result)
150 def ParseOptions(self):
151 """Parses the 'options' field and sets appropriate fields."""
152 if self.options:
153 options = [option.strip() for option in self.options.split(',')]
154 else:
155 options = []
157 for option in options:
158 if option not in VALID_OPTIONS:
159 raise BadConfig('Unrecognized option: %s', option)
161 self.public = PUBLIC in options
162 self.dynamic = DYNAMIC in options
163 self.failfast = FAILFAST in options
164 return self
166 def WriteOptions(self):
167 """Writes the 'options' field based on other settings."""
168 options = []
169 if self.public:
170 options.append('public')
171 if self.dynamic:
172 options.append('dynamic')
173 if self.failfast:
174 options.append('failfast')
175 if options:
176 self.options = ', '.join(options)
177 else:
178 self.options = None
179 return self
182 def LoadBackendEntry(backend_entry):
183 """Parses a BackendEntry object from a string.
185 Args:
186 backend_entry: a backend entry, as a string
188 Returns:
189 A BackendEntry object.
191 builder = yaml_object.ObjectBuilder(BackendEntry)
192 handler = yaml_builder.BuilderHandler(builder)
193 listener = yaml_listener.EventListener(handler)
194 listener.Parse(backend_entry)
196 entries = handler.GetResults()
197 if len(entries) < 1:
198 raise BadConfig('Empty backend configuration.')
199 if len(entries) > 1:
200 raise BadConfig('Multiple backend entries were found in configuration.')
202 return entries[0].Init()
205 class BackendInfoExternal(validation.Validated):
206 """BackendInfoExternal describes all backend entries for an application."""
207 ATTRIBUTES = {
208 BACKENDS: validation.Optional(validation.Repeated(BackendEntry)),
212 def LoadBackendInfo(backend_info, open_fn=None):
213 """Parses a BackendInfoExternal object from a string.
215 Args:
216 backend_info: a backends stanza (list of backends) as a string
217 open_fn: Function for opening files. Unused.
219 Returns:
220 A BackendInfoExternal object.
222 builder = yaml_object.ObjectBuilder(BackendInfoExternal)
223 handler = yaml_builder.BuilderHandler(builder)
224 listener = yaml_listener.EventListener(handler)
225 listener.Parse(backend_info)
227 backend_info = handler.GetResults()
228 if len(backend_info) < 1:
229 return BackendInfoExternal(backends=[])
230 if len(backend_info) > 1:
231 raise BadConfig("Only one 'backends' clause is allowed.")
233 info = backend_info[0]
234 if not info.backends:
235 return BackendInfoExternal(backends=[])
237 for backend in info.backends:
238 backend.Init()
239 return info