1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 from ...utils.caching import HasCachedMethods
17 from ...utils.collections import deepcopy_with_locators
18 from ...utils.formatting import safe_repr
19 from ...utils.type import full_type_name
20 from ...utils.console import puts
21 from ..validation import Issue
22 from .null import none_to_null
23 from .utils import (get_locator, validate_no_short_form, validate_no_unknown_fields,
24 validate_known_fields, validate_primitive)
29 Encapsulates a typed value assignment.
32 def __init__(self, type_name, value, description, required):
33 self.type = deepcopy_with_locators(type_name)
34 self.value = deepcopy_with_locators(value)
35 self.description = deepcopy_with_locators(description)
36 self.required = deepcopy_with_locators(required)
38 def _dump(self, context):
39 if self.type is not None:
40 puts(context.style.type_style(self.type))
41 if self.value is not None:
42 puts(context.style.literal_style(self.value))
43 if self.description is not None:
44 puts(context.style.meta_style(self.description))
45 if self.required is not None:
46 puts(context.style.required_style(self.required))
49 class PresentationBase(HasCachedMethods):
51 Base class for ARIA presentation classes.
54 def __init__(self, name=None, raw=None, container=None):
57 self._container = container
58 super(PresentationBase, self).__init__()
64 def _validate(self, context):
66 Validates the presentation while reporting errors in the validation context but *not*
69 The base class does not thing, but subclasses may override this for specialized validation.
75 Always returns a usable full name of the presentation, whether it itself is named, or
76 recursing to its container, and finally defaulting to the class name.
79 if self._name is not None:
81 elif self._container is not None:
82 return self._container._fullname
83 return full_type_name(self)
88 Attempts to return the most relevant locator, whether we have one, or recursing to our
91 :rtype: :class:`aria.parser.reading.Locator`
94 return get_locator(self._raw, self._container)
96 def _get(self, *names):
98 Gets attributes recursively.
102 if (obj is not None) and names:
104 obj = getattr(obj, name, None)
109 def _get_from_dict(self, *names):
111 Gets attributes recursively, except for the last name which is used to get a value from the
116 obj = self._get(*names[:-1])
117 if isinstance(obj, dict):
118 return obj.get(names[-1]) # pylint: disable=no-member
121 def _get_child_locator(self, *names):
123 Attempts to return the locator of one our children. Will default to our locator if not
126 :rtype: :class:`aria.parser.reading.Locator`
129 if hasattr(self._raw, '_locator'):
130 locator = self._raw._locator
131 if locator is not None:
132 return locator.get_child(*names)
135 def _dump(self, context):
137 Emits a colorized representation.
139 The base class will emit a sensible default representation of the fields, (by calling
140 ``_dump_content``), but subclasses may override this for specialized dumping.
144 puts(context.style.node(self._name))
145 with context.style.indent():
146 self._dump_content(context)
148 self._dump_content(context)
150 def _dump_content(self, context, field_names=None):
152 Emits a colorized representation of the contents.
154 The base class will call ``_dump_field`` on all the fields, but subclasses may override
155 this for specialized dumping.
159 for field_name in field_names:
160 self._dump_field(context, field_name)
161 elif hasattr(self, '_iter_field_names'):
162 for field_name in self._iter_field_names(): # pylint: disable=no-member
163 self._dump_field(context, field_name)
165 puts(context.style.literal_style(self._raw))
167 def _dump_field(self, context, field_name):
169 Emits a colorized representation of the field.
171 According to the field type, this may trigger nested recursion. The nested types will
172 delegate to their ``_dump`` methods.
175 field = self.FIELDS[field_name] # pylint: disable=no-member
176 field.dump(self, context)
178 def _clone(self, container=None):
180 Creates a clone of this presentation, optionally allowing for a new container.
183 raw = deepcopy_with_locators(self._raw)
184 if container is None:
185 container = self._container
186 return self.__class__(name=self._name, raw=raw, container=container)
189 class Presentation(PresentationBase):
191 Base class for ARIA presentations. A presentation is a Pythonic wrapper around agnostic raw
192 data, adding the ability to read and modify the data with proper validation.
194 ARIA presentation classes will often be decorated with :func:`has_fields`, as that mechanism
195 automates a lot of field-specific validation. However, that is not a requirement.
197 Make sure that your utility property and method names begin with a ``_``, because those names
198 without a ``_`` prefix are normally reserved for fields.
201 def _validate(self, context):
202 validate_no_short_form(context, self)
203 validate_no_unknown_fields(context, self)
204 validate_known_fields(context, self)
207 class AsIsPresentation(PresentationBase):
209 Base class for trivial ARIA presentations that provide the raw value as is.
212 def __init__(self, name=None, raw=None, container=None, cls=None):
213 super(AsIsPresentation, self).__init__(name, raw, container)
218 return none_to_null(self._raw)
221 def value(self, value):
225 def _full_cls_name(self):
226 name = full_type_name(self.cls) if self.cls is not None else None
227 if name == 'unicode':
228 # For simplicity, display "unicode" as "str"
232 def _validate(self, context):
234 validate_primitive(self._raw, self.cls, context.validation.allow_primitive_coersion)
235 except ValueError as e:
236 context.validation.report('"%s" is not a valid "%s": %s'
237 % (self._fullname, self._full_cls_name, safe_repr(self._raw)),
238 locator=self._locator,
242 def _dump(self, context):
243 if hasattr(self._raw, '_dump'):
244 puts(context.style.node(self._name))
245 with context.style.indent():
246 self._raw._dump(context)
248 super(AsIsPresentation, self)._dump(context)