| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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. |
| |
| from ...utils.caching import HasCachedMethods |
| from ...utils.collections import deepcopy_with_locators |
| from ...utils.formatting import safe_repr |
| from ...utils.type import full_type_name |
| from ...utils.console import puts |
| from ..validation import Issue |
| from .null import none_to_null |
| from .utils import (get_locator, validate_no_short_form, validate_no_unknown_fields, |
| validate_known_fields, validate_primitive) |
| |
| |
| class Value(object): |
| """ |
| Encapsulates a typed value assignment. |
| """ |
| |
| def __init__(self, type_name, value, description, required): |
| self.type = deepcopy_with_locators(type_name) |
| self.value = deepcopy_with_locators(value) |
| self.description = deepcopy_with_locators(description) |
| self.required = deepcopy_with_locators(required) |
| |
| def _dump(self, context): |
| if self.type is not None: |
| puts(context.style.type_style(self.type)) |
| if self.value is not None: |
| puts(context.style.literal_style(self.value)) |
| if self.description is not None: |
| puts(context.style.meta_style(self.description)) |
| if self.required is not None: |
| puts(context.style.required_style(self.required)) |
| |
| |
| class PresentationBase(HasCachedMethods): |
| """ |
| Base class for ARIA presentation classes. |
| """ |
| |
| def __init__(self, name=None, raw=None, container=None): |
| self._name = name |
| self._raw = raw |
| self._container = container |
| super(PresentationBase, self).__init__() |
| |
| @property |
| def as_raw(self): |
| return self._raw |
| |
| def _validate(self, context): |
| """ |
| Validates the presentation while reporting errors in the validation context but *not* |
| raising exceptions. |
| |
| The base class does not thing, but subclasses may override this for specialized validation. |
| """ |
| |
| @property |
| def _fullname(self): |
| """ |
| Always returns a usable full name of the presentation, whether it itself is named, or |
| recursing to its container, and finally defaulting to the class name. |
| """ |
| |
| if self._name is not None: |
| return self._name |
| elif self._container is not None: |
| return self._container._fullname |
| return full_type_name(self) |
| |
| @property |
| def _locator(self): |
| """ |
| Attempts to return the most relevant locator, whether we have one, or recursing to our |
| container. |
| |
| :rtype: :class:`aria.parser.reading.Locator` |
| """ |
| |
| return get_locator(self._raw, self._container) |
| |
| def _get(self, *names): |
| """ |
| Gets attributes recursively. |
| """ |
| |
| obj = self |
| if (obj is not None) and names: |
| for name in names: |
| obj = getattr(obj, name, None) |
| if obj is None: |
| break |
| return obj |
| |
| def _get_from_dict(self, *names): |
| """ |
| Gets attributes recursively, except for the last name which is used to get a value from the |
| last dict. |
| """ |
| |
| if names: |
| obj = self._get(*names[:-1]) |
| if isinstance(obj, dict): |
| return obj.get(names[-1]) # pylint: disable=no-member |
| return None |
| |
| def _get_child_locator(self, *names): |
| """ |
| Attempts to return the locator of one our children. Will default to our locator if not |
| found. |
| |
| :rtype: :class:`aria.parser.reading.Locator` |
| """ |
| |
| if hasattr(self._raw, '_locator'): |
| locator = self._raw._locator |
| if locator is not None: |
| return locator.get_child(*names) |
| return self._locator |
| |
| def _dump(self, context): |
| """ |
| Emits a colorized representation. |
| |
| The base class will emit a sensible default representation of the fields, (by calling |
| ``_dump_content``), but subclasses may override this for specialized dumping. |
| """ |
| |
| if self._name: |
| puts(context.style.node(self._name)) |
| with context.style.indent(): |
| self._dump_content(context) |
| else: |
| self._dump_content(context) |
| |
| def _dump_content(self, context, field_names=None): |
| """ |
| Emits a colorized representation of the contents. |
| |
| The base class will call ``_dump_field`` on all the fields, but subclasses may override |
| this for specialized dumping. |
| """ |
| |
| if field_names: |
| for field_name in field_names: |
| self._dump_field(context, field_name) |
| elif hasattr(self, '_iter_field_names'): |
| for field_name in self._iter_field_names(): # pylint: disable=no-member |
| self._dump_field(context, field_name) |
| else: |
| puts(context.style.literal_style(self._raw)) |
| |
| def _dump_field(self, context, field_name): |
| """ |
| Emits a colorized representation of the field. |
| |
| According to the field type, this may trigger nested recursion. The nested types will |
| delegate to their ``_dump`` methods. |
| """ |
| |
| field = self.FIELDS[field_name] # pylint: disable=no-member |
| field.dump(self, context) |
| |
| def _clone(self, container=None): |
| """ |
| Creates a clone of this presentation, optionally allowing for a new container. |
| """ |
| |
| raw = deepcopy_with_locators(self._raw) |
| if container is None: |
| container = self._container |
| return self.__class__(name=self._name, raw=raw, container=container) |
| |
| |
| class Presentation(PresentationBase): |
| """ |
| Base class for ARIA presentations. A presentation is a Pythonic wrapper around agnostic raw |
| data, adding the ability to read and modify the data with proper validation. |
| |
| ARIA presentation classes will often be decorated with :func:`has_fields`, as that mechanism |
| automates a lot of field-specific validation. However, that is not a requirement. |
| |
| Make sure that your utility property and method names begin with a ``_``, because those names |
| without a ``_`` prefix are normally reserved for fields. |
| """ |
| |
| def _validate(self, context): |
| validate_no_short_form(context, self) |
| validate_no_unknown_fields(context, self) |
| validate_known_fields(context, self) |
| |
| |
| class AsIsPresentation(PresentationBase): |
| """ |
| Base class for trivial ARIA presentations that provide the raw value as is. |
| """ |
| |
| def __init__(self, name=None, raw=None, container=None, cls=None): |
| super(AsIsPresentation, self).__init__(name, raw, container) |
| self.cls = cls |
| |
| @property |
| def value(self): |
| return none_to_null(self._raw) |
| |
| @value.setter |
| def value(self, value): |
| self._raw = value |
| |
| @property |
| def _full_cls_name(self): |
| name = full_type_name(self.cls) if self.cls is not None else None |
| if name == 'unicode': |
| # For simplicity, display "unicode" as "str" |
| name = 'str' |
| return name |
| |
| def _validate(self, context): |
| try: |
| validate_primitive(self._raw, self.cls, context.validation.allow_primitive_coersion) |
| except ValueError as e: |
| context.validation.report('"%s" is not a valid "%s": %s' |
| % (self._fullname, self._full_cls_name, safe_repr(self._raw)), |
| locator=self._locator, |
| level=Issue.FIELD, |
| exception=e) |
| |
| def _dump(self, context): |
| if hasattr(self._raw, '_dump'): |
| puts(context.style.node(self._name)) |
| with context.style.indent(): |
| self._raw._dump(context) |
| else: |
| super(AsIsPresentation, self)._dump(context) |