| # 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.threading import FixedThreadPoolExecutor |
| from ...utils.formatting import json_dumps, yaml_dumps |
| from ..loading import UriLocation |
| from ..reading import AlreadyReadException |
| from ..presentation import PresenterNotFoundError |
| from .consumer import Consumer |
| |
| |
| class Read(Consumer): |
| """ |
| Reads the presentation, handling imports recursively. |
| |
| It works by consuming a data source via appropriate :class:`~aria.parser.loading.Loader`, |
| :class:`~aria.parser.reading.Reader`, and :class:`~aria.parser.presentation.Presenter` |
| instances. |
| |
| It supports agnostic raw data composition for presenters that have |
| ``_get_import_locations`` and ``_merge_import``. |
| |
| To improve performance, loaders are called asynchronously on separate threads. |
| |
| Note that parsing may internally trigger more than one loading/reading/presentation |
| cycle, for example if the agnostic raw data has dependencies that must also be parsed. |
| """ |
| |
| def consume(self): |
| if self.context.presentation.location is None: |
| self.context.validation.report('Presentation consumer: missing location') |
| return |
| |
| presenter = None |
| imported_presentations = None |
| |
| executor = FixedThreadPoolExecutor(size=self.context.presentation.threads, |
| timeout=self.context.presentation.timeout) |
| executor.print_exceptions = self.context.presentation.print_exceptions |
| try: |
| presenter = self._present(self.context.presentation.location, None, None, executor) |
| executor.drain() |
| |
| # Handle exceptions |
| for e in executor.exceptions: |
| self._handle_exception(e) |
| |
| imported_presentations = executor.returns |
| finally: |
| executor.close() |
| |
| # Merge imports |
| if (imported_presentations is not None) and hasattr(presenter, '_merge_import'): |
| for imported_presentation in imported_presentations: |
| okay = True |
| if hasattr(presenter, '_validate_import'): |
| okay = presenter._validate_import(self.context, imported_presentation) |
| if okay: |
| presenter._merge_import(imported_presentation) |
| |
| self.context.presentation.presenter = presenter |
| |
| def dump(self): |
| if self.context.has_arg_switch('yaml'): |
| indent = self.context.get_arg_value_int('indent', 2) |
| raw = self.context.presentation.presenter._raw |
| self.context.write(yaml_dumps(raw, indent=indent)) |
| elif self.context.has_arg_switch('json'): |
| indent = self.context.get_arg_value_int('indent', 2) |
| raw = self.context.presentation.presenter._raw |
| self.context.write(json_dumps(raw, indent=indent)) |
| else: |
| self.context.presentation.presenter._dump(self.context) |
| |
| def _handle_exception(self, e): |
| if isinstance(e, AlreadyReadException): |
| return |
| super(Read, self)._handle_exception(e) |
| |
| def _present(self, location, origin_location, presenter_class, executor): |
| # Link the context to this thread |
| self.context.set_thread_local() |
| |
| raw = self._read(location, origin_location) |
| |
| if self.context.presentation.presenter_class is not None: |
| # The presenter class we specified in the context overrides everything |
| presenter_class = self.context.presentation.presenter_class |
| else: |
| try: |
| presenter_class = self.context.presentation.presenter_source.get_presenter(raw) |
| except PresenterNotFoundError: |
| if presenter_class is None: |
| raise |
| # We'll use the presenter class we were given (from the presenter that imported us) |
| if presenter_class is None: |
| raise PresenterNotFoundError('presenter not found') |
| |
| presentation = presenter_class(raw=raw) |
| |
| if presentation is not None and hasattr(presentation, '_link_locators'): |
| presentation._link_locators() |
| |
| # Submit imports to executor |
| if hasattr(presentation, '_get_import_locations'): |
| import_locations = presentation._get_import_locations(self.context) |
| if import_locations: |
| for import_location in import_locations: |
| # The imports inherit the parent presenter class and use the current location as |
| # their origin location |
| import_location = UriLocation(import_location) |
| executor.submit(self._present, import_location, location, presenter_class, |
| executor) |
| |
| return presentation |
| |
| def _read(self, location, origin_location): |
| if self.context.reading.reader is not None: |
| return self.context.reading.reader.read() |
| loader = self.context.loading.loader_source.get_loader(self.context.loading, location, |
| origin_location) |
| reader = self.context.reading.reader_source.get_reader(self.context.reading, location, |
| loader) |
| return reader.read() |