Skip to content

Improve error message #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Changelog
1.3 (unreleased)
----------------

- Allow to pass an error_callback for the GracefulResourceRenderer.
This allows to run some code in an error case, e.g. to add a user-visible
status message.
[thet]

- Also handle resource resolver errors gracefully in the GracefulResourceRenderer.
[thet]

- Improve error messages by including more detailed information.
[thet]

- Do not wrap resource ``__repr__`` output in ``<>`` to render tracebacks
properly in browser.
[lenadax]
Expand Down Expand Up @@ -68,7 +79,7 @@ Changelog
Modernize setup.[py|cfg].
[jensens]

- Added ``GracefulResourceRenderer``.
- Added ``GracefulResourceRenderer``.
Fixes #1.
[jensens]

Expand Down
100 changes: 70 additions & 30 deletions webresource/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,36 @@ def include(self, include):
def remove(self):
"""Remove resource or resource group from parent group."""
if not self.parent:
raise ResourceError('Object is no member of a resource group')
raise ResourceError(
'Cannot remove resource or resource group {}.'
' It is no member of a resource group.'.format(self)
)
self.parent.members.remove(self)
self.parent = None

def copy(self):
"""Return a deep copy of this object."""
return copy.deepcopy(self)

def __repr__(self):
"""Return a string representation of the resource or resource group."""

reprs = []
if getattr(self, "name", None):
reprs.append('name="{}"'.format(self.name))
elif getattr(self, "path", None):
reprs.append('path="{}"'.format(self.path))
elif getattr(self, "url", None):
reprs.append('url="{}"'.format(self.url))

if getattr(self, "depends", None):
reprs.append('depends="{}"'.format(self.depends))

return '<{} {}>'.format(
self.__class__.__name__,
", ".join(reprs) if reprs else "unnamed"
)


class Resource(ResourceMixin):
"""A web resource."""
Expand Down Expand Up @@ -135,12 +157,16 @@ def __init__(
additional attributes on resource tag.
:raise ResourceError: No resource and no url given.
"""
if resource is None and url is None:
raise ResourceError('Either resource or url must be given')
super(Resource, self).__init__(
name=name, directory=directory, path=path,
include=include, group=group
)
if resource is None and url is None:
raise ResourceError(
'Either resource or url must be given for resource {}'.format(
self
)
)
self.depends = (
(depends if isinstance(depends, (list, tuple)) else [depends])
if depends else None
Expand Down Expand Up @@ -169,7 +195,9 @@ def file_path(self):
"""Absolute resource file path depending on operation mode."""
directory = self.directory
if not directory:
raise ResourceError('No directory set on resource.')
raise ResourceError(
'No directory set on resource {}'.format(self)
)
return os.path.join(directory, self.file_name)

@property
Expand Down Expand Up @@ -235,15 +263,6 @@ def _render_tag(self, tag, closing_tag, **attrs):
return u'<{tag}{attrs} />'.format(tag=tag, attrs=attrs_)
return u'<{tag}{attrs}></{tag}>'.format(tag=tag, attrs=attrs_)

def __repr__(self):
return (
'{} name="{}", depends="{}"'
).format(
self.__class__.__name__,
self.name,
self.depends
)


class ScriptResource(Resource):
"""A Javascript resource."""
Expand Down Expand Up @@ -323,8 +342,10 @@ def integrity(self):
def integrity(self, integrity):
if integrity is True:
if self.url is not None:
msg = 'Cannot calculate integrity hash from external resource'
raise ResourceError(msg)
raise ResourceError(
'Cannot calculate integrity hash from external resource '
'{}'.format(self)
)
self._integrity_hash = None
else:
self._integrity_hash = integrity
Expand Down Expand Up @@ -564,8 +585,8 @@ def add(self, member):
"""
if not isinstance(member, (ResourceGroup, Resource)):
raise ResourceError(
'Resource group can only contain instances '
'of ``ResourceGroup`` or ``Resource``'
'Resource group {} can only contain instances '
'of ``ResourceGroup`` or ``Resource``'.format(self)
)
member.parent = self
self._members.append(member)
Expand All @@ -584,12 +605,6 @@ def _filtered_resources(self, type_, members=None):
resources.append(member)
return resources

def __repr__(self):
return '{} name="{}"'.format(
self.__class__.__name__,
self.name
)


class ResourceConflictError(ResourceError):
"""Multiple resources declared with the same name."""
Expand Down Expand Up @@ -634,7 +649,7 @@ def __init__(self, members):
for member in members:
if not isinstance(member, (Resource, ResourceGroup)):
raise ResourceError(
'members can only contain instances '
'ResourceResolver members can only contain instances '
'of ``ResourceGroup`` or ``Resource``'
)
self.members = members
Expand Down Expand Up @@ -723,13 +738,38 @@ def render(self):
class GracefulResourceRenderer(ResourceRenderer):
"""Resource renderer, which does not fail but logs an exception."""

def render(self):
def render(self, error_callback=None):
lines = []
for resource in self.resolver.resolve():
resources = []

try:
resources = self.resolver.resolve()
except (
ResourceConflictError,
ResourceCircularDependencyError,
ResourceMissingDependencyError,
) as e:
error_message = str(e)
logger.exception(error_message)
if error_callback:
error_callback(error_message)

for resource in resources:
error_message = None
try:
lines.append(resource.render(self.base_url))
except (ResourceError, FileNotFoundError):
msg = u'Failure to render resource "{}"'.format(resource.name)
lines.append(u'<!-- {} - details in logs -->'.format(msg))
logger.exception(msg)
except FileNotFoundError:
error_message = u'File not found for resource {}'.format(
resource
)
except ResourceError as e:
error_message = str(e)
finally:
if error_message:
lines.append(u'<!-- {} - details in logs -->'.format(
error_message
))
logger.exception(error_message)
if error_callback:
error_callback(error_message)
return u'\n'.join(lines)
Loading
Loading