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.
22 A library for working with BackendInfoExternal records, describing backends
23 configured for an application. Supports loading the records from backend.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
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)$'
60 INSTANCES
= 'instances'
65 MAX_CONCURRENT_REQUESTS
= 'max_concurrent_requests'
68 VALID_OPTIONS
= frozenset([PUBLIC
, DYNAMIC
, FAILFAST
])
73 class BadConfig(Exception):
74 """An invalid configuration was provided."""
76 class ListWithoutSort(list):
80 class SortedDict(dict):
81 def __init__(self
, keys
, data
):
82 super(SortedDict
, self
).__init
__()
87 result
= ListWithoutSort()
89 if type(self
.get(key
)) != type(None):
90 result
.append((key
, self
.get(key
)))
93 representer
.SafeRepresenter
.add_representer(
94 SortedDict
, representer
.SafeRepresenter
.represent_dict
)
96 class BackendEntry(validation
.Validated
):
97 """A backend entry describes a single backend."""
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
)
121 raise BadConfig("Illegal field: 'public'")
123 raise BadConfig("Illegal field: 'dynamic'")
125 raise BadConfig("Illegal field: 'failfast'")
129 def set_class(self
, Class
):
130 """Setter for 'class', since an attribute reference is an error."""
131 self
.Set(CLASS
, Class
)
134 """Accessor for 'class', since an attribute reference is an error."""
135 return self
.Get(CLASS
)
138 """Returns a sorted dictionary representing the backend entry."""
139 self
.ParseOptions().WriteOptions()
140 result
= super(BackendEntry
, self
).ToDict()
141 return SortedDict([NAME
,
146 MAX_CONCURRENT_REQUESTS
,
150 def ParseOptions(self
):
151 """Parses the 'options' field and sets appropriate fields."""
153 options
= [option
.strip() for option
in self
.options
.split(',')]
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
166 def WriteOptions(self
):
167 """Writes the 'options' field based on other settings."""
170 options
.append('public')
172 options
.append('dynamic')
174 options
.append('failfast')
176 self
.options
= ', '.join(options
)
182 def LoadBackendEntry(backend_entry
):
183 """Parses a BackendEntry object from a string.
186 backend_entry: a backend entry, as a string
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()
198 raise BadConfig('Empty backend configuration.')
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."""
208 BACKENDS
: validation
.Optional(validation
.Repeated(BackendEntry
)),
212 def LoadBackendInfo(backend_info
, open_fn
=None):
213 """Parses a BackendInfoExternal object from a string.
216 backend_info: a backends stanza (list of backends) as a string
217 open_fn: Function for opening files. Unused.
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
: