blob: 542b3f052557bfb5b73dbdd83e24d7a7ca2df2cb [file] [log] [blame]
# 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()