8000 [pull] main from google:main by pull[bot] · Pull Request #102 · kp-forks/pyglove · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[pull] main from google:main #102

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

Merged
merged 1 commit into from
May 16, 2025
Merged
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
8000
Diff view
17 changes: 10 additions & 7 deletions docs/api/docgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def _load_default_template(self, filename: str) -> jinja2.Template:


@dataclasses.dataclass
class Api:
class Api(metaclass=abc.ABCMeta):
"""Base class for an API entry."""
name: str
qualname: Optional[str]
Expand Down Expand Up @@ -156,7 +156,6 @@ def relative_handle(self, base_handle: str) -> str:
return os.path.relpath(self.doc_handle, base_handle)

@property
@abc.abstractmethod
def doc_dir(self) -> str:
dir_segments = self.canonical_path.split('.')[1:]
if not dir_segments:
Expand Down Expand Up @@ -220,7 +219,7 @@ def __post_init__(self):
self._doc_handlename = None

@property
def doc_handlename(self) -> str:
def doc_handlename(self) -> Optional[str]:
return self._doc_handlename

def set_doc_handlename(self, doc_handlename: str) -> None:
Expand All @@ -238,6 +237,7 @@ def canonical_path(self) -> str:
@property
def doc_handle(self) -> str:
"""Returns doc handle (e.g. core/typing/dict) relative to base_handle."""
assert self.doc_handlename is not None, self
return os.path.join(self.doc_dir, self.doc_handlename)

@abc.abstractmethod
Expand Down Expand Up @@ -305,6 +305,7 @@ class NamedEntry:
api: Api


@dataclasses.dataclass
class Module(Api):
"""API entry for a module."""

Expand All @@ -315,7 +316,7 @@ def __post_init__(self):

def __getitem__(self, key):
"""Returns child API entry."""
return self._name_to_api[key]
return self._name_to_api[key] # pytype: disable=attribute-error

@property
def template_variable(self) -> str:
Expand Down Expand Up @@ -370,7 +371,7 @@ def modules(self) -> List[NamedEntry]:
def children(self) -> List[NamedEntry]:
return self._children

def all_apis(self, memo: Optional[Set[id]] = None) -> List[Leaf]:
def all_apis(self, memo: Optional[Set[int]] = None) -> List[Leaf]:
"""Returns all leaf APIs."""
if memo is None:
memo = set()
Expand All @@ -380,7 +381,9 @@ def all_apis(self, memo: Optional[Set[id]] = None) -> List[Leaf]:
apis.append(c.api)
memo.add(id(c))
for m in self.modules:
apis.extend(m.api.all_apis(memo))
module_api = m.api
assert isinstance(module_api, Module)
apis.extend(module_api.all_apis(memo))
return apis


Expand Down Expand Up @@ -409,7 +412,7 @@ def get_api_from_module(module, path):
child_api = Function(
child.__name__, f'{child.__module__}.{child.__name__}')
elif (not inspect.ismodule(child)
and not isinstance(child, typing._Final)): # pylint: disable=protected-access
and not isinstance(child, typing._Final)): # pytype: disable=module-attr # pylint: disable=protected-access
child_api = Object(name, None)
if child_api:
symbol_to_api[id(child)] = child_api
Expand Down
4 changes: 3 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import re
import sys

from sphinx.domains.python import PyXRefRole
from sphinx.domains import python as sphinx_python

PyXRefRole = sphinx_python.PyXRefRole


# Set parent directory to path in order to import pyglove.
Expand Down
1 change: 1 addition & 0 deletions pyglove/core/symbolic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,6 @@

# Error types.
from pyglove.core.symbolic.base import WritePermissionError
from pyglove.core.symbolic.error_info import ErrorInfo

# pylint: enable=g-bad-import-order
127 changes: 127 additions & 0 deletions pyglove/core/symbolic/error_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright 2025 The PyGlove Authors
#
# Licensed 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.
"""Symbolic class for capture error information."""

from typing import Annotated, List
from pyglove.core import utils
from pyglove.core.symbolic import dict as pg_dict # pylint: disable=unused-import
from pyglove.core.symbolic import list as pg_list # pylint: disable=unused-import
from pyglove.core.symbolic import object as pg_object
from pyglove.core.views.html import controls
from pyglove.core.views.html import tree_view


@pg_object.members(
[],
# For backwards compatibility.
additional_keys=['pyglove.core.utils.error_utils.ErrorInfo']
)
class ErrorInfo(
pg_object.Object,
utils.ErrorInfo,
tree_view.HtmlTreeView.Extension
):
"""Symbolic error info."""

tag: Annotated[
str,
(
'A path of the error types in the exception chain. For example, '
'`ValueError.ZeroDivisionError` means the error is a '
'`ZeroDivisionError` raised at the first place and then reraised as '
'a `ValueError`.'
)
]

description: Annotated[
str,
'The description of the error.',
]

stacktrace: Annotated[
str,
'The stacktrace of the error.',
]

def _html_tree_view_summary(
self,
*,
view: tree_view.HtmlTreeView,
**kwargs
) -> tree_view.Html:
kwargs.pop('title', None)
kwargs.pop('enable_summary_tooltip', None)
return view.summary(
self,
title=tree_view.Html.element(
'div',
[
'ErrorInfo',
controls.Label(self.tag, css_classes=['error_tag']),
],
styles=dict(display='inline-block')
),
enable_summary_tooltip=False,
**kwargs
)

def _html_tree_view_content(self, **kwargs) -> tree_view.Html:
del kwargs
return controls.TabControl([
controls.Tab(
'description',
tree_view.Html.element(
'div',
[
tree_view.Html.escape(utils.decolor(self.description))
],
css_classes=['error_text']
)
),
controls.Tab(
'stacktrace',
tree_view.Html.element(
'div',
[
tree_view.Html.escape(utils.decolor(self.stacktrace))
],
css_classes=['error_text']
)
)
]).to_html()

@classmethod
def _html_tree_view_css_styles(cls) -> List[str]:
return super()._html_tree_view_css_styles() + [
'''
.error_tag {
background-color: red;
border-radius: 5px;
padding: 5px;
margin-left: 5px;
color: white;
font-size: small;
}
.error_text {
display: block;
white-space: pre-wrap;
padding: 0.5em;
margin-top: 0.15em;
background-color: #f2f2f2;
}
'''
]

# Use the symbolic error info class as the default ErrorInfo implementation.
utils.ErrorInfo._IMPLEMENTATION = ErrorInfo # pylint: disable=protected-access
92 changes: 92 additions & 0 deletions pyglove/core/symbolic/error_info_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2025 The PyGlove Authors
#
# Licensed 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.

import inspect
import unittest
from pyglove.core.symbolic import base
from pyglove.core.symbolic import error_info as error_info_lib # pylint: disable=g-bad-import-order


class ErrorInfoTest(unittest.TestCase):
"""Tests for ErrorInfo."""

def test_from_exception(self):

def foo():
return 1 / 0

def bar():
try:
foo()
except ZeroDivisionError as e:
raise ValueError('Bad call to `foo`') from e

error_info = None
try:
bar()
except ValueError as e:
error_info = error_info_lib.ErrorInfo.from_exception(e)
self.assertIsNotNone(error_info)
self.assertEqual(error_info.tag, 'ValueError.ZeroDivisionError')
self.assertEqual(error_info.description, 'Bad call to `foo`')
self.assertIn('Traceback (most recent call last)', error_info.stacktrace)

def test_to_json(self):
error_info = error_info_lib.ErrorInfo(
tag='ValueError.ZeroDivisionError',
description='Bad call to `foo`',
stacktrace='Traceback (most recent call last)',
)
json_dict = error_info.to_json()
error_info2 = base.from_json(json_dict)
self.assertIsNot(error_info2, error_info)
self.assertEqual(error_info2, error_info)
json_dict['_type'] = 'pyglove.core.utils.error_utils.ErrorInfo'
error_info2 = base.from_json(json_dict)
self.assertEqual(error_info2, error_info)

def test_format(self):
error_info = error_info_lib.ErrorInfo(
tag='ValueError.ZeroDivisionError',
description='Bad call to `foo`',
stacktrace='Traceback (most recent call last)',
)
self.assertEqual(
error_info.format(compact=False),
inspect.cleandoc(
"""
ErrorInfo(
tag = 'ValueError.ZeroDivisionError',
description = 'Bad call to `foo`',
stacktrace = 'Traceback (most recent call last)'
)
"""
)
)

def test_to_html(self):
error_info = error_info_lib.ErrorInfo(
tag='ValueError.ZeroDivisionError',
description='Bad call to `foo`',
stacktrace='Traceback (most recent call last)',
)
html = error_info.to_html()
self.assertIn('ErrorInfo', html.content)
self.assertIn('ValueError.ZeroDivisionError', html.content)
self.assertIn('Bad call to `foo`', html.content)
self.assertIn('Traceback (most recent call last)', html.content)


if __name__ == '__main__':
unittest.main()
Loading
0