pyglove 0.4.5.dev20240318__py3-none-any.whl → 0.4.5.dev202501132210__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglove/core/__init__.py +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240318.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
# Copyright 2022 The PyGlove Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# pylint: disable=line-too-long
|
15
|
+
"""Utility library that provides common traits for objects in Python.
|
16
|
+
|
17
|
+
Overview
|
18
|
+
--------
|
19
|
+
|
20
|
+
``pg.utils`` sits at the bottom of all PyGlove modules and empowers other
|
21
|
+
modules with the following features:
|
22
|
+
|
23
|
+
+---------------------+--------------------------------------------+
|
24
|
+
| Functionality | API |
|
25
|
+
+=====================+============================================+
|
26
|
+
| Formatting | :class:`pg.Formattable`, |
|
27
|
+
| | |
|
28
|
+
| | :func:`pg.format`, |
|
29
|
+
| | |
|
30
|
+
| | :func:`pg.print`, |
|
31
|
+
| | |
|
32
|
+
| | :func:`pg.utils.kvlist_str`, |
|
33
|
+
| | |
|
34
|
+
| | :func:`pg.utils.quote_if_str`, |
|
35
|
+
| | |
|
36
|
+
| | :func:`pg.utils.message_on_path` |
|
37
|
+
+---------------------+--------------------------------------------+
|
38
|
+
| Serialization | :class:`pg.JSONConvertible`, |
|
39
|
+
| | |
|
40
|
+
| | :func:`pg.registered_types`, |
|
41
|
+
| | |
|
42
|
+
| | :func:`pg.utils.to_json`, |
|
43
|
+
| | |
|
44
|
+
| | :func:`pg.utils.from_json`, |
|
45
|
+
+---------------------+--------------------------------------------+
|
46
|
+
| Partial construction| :class:`pg.MaybePartial`, |
|
47
|
+
| | |
|
48
|
+
| | :const:`pg.MISSING_VALUE`. |
|
49
|
+
+---------------------+--------------------------------------------+
|
50
|
+
| Hierarchical key | :class:`pg.KeyPath` |
|
51
|
+
| representation | |
|
52
|
+
+---------------------+--------------------------------------------+
|
53
|
+
| Hierarchical object | :func:`pg.utils.traverse` |
|
54
|
+
| traversal | |
|
55
|
+
+---------------------+--------------------------------------------+
|
56
|
+
| Hierarchical object | :func:`pg.utils.transform`, |
|
57
|
+
| transformation | |
|
58
|
+
| | :func:`pg.utils.merge`, |
|
59
|
+
| | |
|
60
|
+
| | :func:`pg.utils.canonicalize`, |
|
61
|
+
| | |
|
62
|
+
| | :func:`pg.utils.flatten` |
|
63
|
+
+---------------------+--------------------------------------------+
|
64
|
+
| Docstr handling | :class:`pg.docstr`, |
|
65
|
+
+---------------------+--------------------------------------------+
|
66
|
+
| Error handling | :class:`pg.catch_errors`, |
|
67
|
+
+---------------------+--------------------------------------------+
|
68
|
+
"""
|
69
|
+
# pylint: enable=line-too-long
|
70
|
+
# pylint: disable=g-bad-import-order
|
71
|
+
# pylint: disable=g-importing-member
|
72
|
+
|
73
|
+
# Handling JSON conversion.
|
74
|
+
from pyglove.core.utils.json_conversion import Nestable
|
75
|
+
from pyglove.core.utils.json_conversion import JSONValueType
|
76
|
+
|
77
|
+
from pyglove.core.utils.json_conversion import JSONConvertible
|
78
|
+
from pyglove.core.utils.json_conversion import from_json
|
79
|
+
from pyglove.core.utils.json_conversion import to_json
|
80
|
+
from pyglove.core.utils.json_conversion import registered_types
|
81
|
+
|
82
|
+
# Handling formatting.
|
83
|
+
from pyglove.core.utils.formatting import Formattable
|
84
|
+
from pyglove.core.utils.formatting import format # pylint: disable=redefined-builtin
|
85
|
+
from pyglove.core.utils.formatting import printv as print # pylint: disable=redefined-builtin
|
86
|
+
from pyglove.core.utils.formatting import kvlist_str
|
87
|
+
from pyglove.core.utils.formatting import quote_if_str
|
88
|
+
from pyglove.core.utils.formatting import maybe_markdown_quote
|
89
|
+
from pyglove.core.utils.formatting import comma_delimited_str
|
90
|
+
from pyglove.core.utils.formatting import camel_to_snake
|
91
|
+
from pyglove.core.utils.formatting import auto_plural
|
92
|
+
from pyglove.core.utils.formatting import BracketType
|
93
|
+
from pyglove.core.utils.formatting import bracket_chars
|
94
|
+
from pyglove.core.utils.formatting import RawText
|
95
|
+
|
96
|
+
# Context managers for defining the default format for __str__ and __repr__.
|
97
|
+
from pyglove.core.utils.formatting import str_format
|
98
|
+
from pyglove.core.utils.formatting import repr_format
|
99
|
+
|
100
|
+
# Value location.
|
101
|
+
from pyglove.core.utils.value_location import KeyPath
|
102
|
+
from pyglove.core.utils.value_location import KeyPathSet
|
103
|
+
from pyglove.core.utils.value_location import StrKey
|
104
|
+
from pyglove.core.utils.value_location import message_on_path
|
105
|
+
|
106
|
+
# Value markers.
|
107
|
+
from pyglove.core.utils.missing import MissingValue
|
108
|
+
from pyglove.core.utils.missing import MISSING_VALUE
|
109
|
+
|
110
|
+
# Handling hierarchical.
|
111
|
+
from pyglove.core.utils.hierarchical import traverse
|
112
|
+
from pyglove.core.utils.hierarchical import transform
|
113
|
+
from pyglove.core.utils.hierarchical import flatten
|
114
|
+
from pyglove.core.utils.hierarchical import canonicalize
|
115
|
+
from pyglove.core.utils.hierarchical import merge
|
116
|
+
from pyglove.core.utils.hierarchical import merge_tree
|
117
|
+
from pyglove.core.utils.hierarchical import is_partial
|
118
|
+
from pyglove.core.utils.hierarchical import try_listify_dict_with_int_keys
|
119
|
+
|
120
|
+
# Common traits.
|
121
|
+
from pyglove.core.utils.common_traits import MaybePartial
|
122
|
+
from pyglove.core.utils.common_traits import Functor
|
123
|
+
|
124
|
+
from pyglove.core.utils.common_traits import explicit_method_override
|
125
|
+
from pyglove.core.utils.common_traits import ensure_explicit_method_override
|
126
|
+
|
127
|
+
# Handling thread local values.
|
128
|
+
from pyglove.core.utils.thread_local import thread_local_value_scope
|
129
|
+
from pyglove.core.utils.thread_local import thread_local_has
|
130
|
+
from pyglove.core.utils.thread_local import thread_local_set
|
131
|
+
from pyglove.core.utils.thread_local import thread_local_get
|
132
|
+
from pyglove.core.utils.thread_local import thread_local_del
|
133
|
+
from pyglove.core.utils.thread_local import thread_local_increment
|
134
|
+
from pyglove.core.utils.thread_local import thread_local_decrement
|
135
|
+
from pyglove.core.utils.thread_local import thread_local_push
|
136
|
+
from pyglove.core.utils.thread_local import thread_local_pop
|
137
|
+
from pyglove.core.utils.thread_local import thread_local_peek
|
138
|
+
|
139
|
+
# Handling docstrings.
|
140
|
+
from pyglove.core.utils.docstr_utils import DocStr
|
141
|
+
from pyglove.core.utils.docstr_utils import DocStrStyle
|
142
|
+
from pyglove.core.utils.docstr_utils import DocStrEntry
|
143
|
+
from pyglove.core.utils.docstr_utils import DocStrExample
|
144
|
+
from pyglove.core.utils.docstr_utils import DocStrArgument
|
145
|
+
from pyglove.core.utils.docstr_utils import DocStrReturns
|
146
|
+
from pyglove.core.utils.docstr_utils import DocStrRaises
|
147
|
+
from pyglove.core.utils.docstr_utils import docstr
|
148
|
+
|
149
|
+
# Handling exceptions.
|
150
|
+
from pyglove.core.utils.error_utils import catch_errors
|
151
|
+
from pyglove.core.utils.error_utils import CatchErrorsContext
|
152
|
+
from pyglove.core.utils.error_utils import ErrorInfo
|
153
|
+
|
154
|
+
# Timing.
|
155
|
+
from pyglove.core.utils.timing import timeit
|
156
|
+
from pyglove.core.utils.timing import TimeIt
|
157
|
+
|
158
|
+
# Text color.
|
159
|
+
from pyglove.core.utils.text_color import colored
|
160
|
+
from pyglove.core.utils.text_color import colored_block
|
161
|
+
from pyglove.core.utils.text_color import decolor
|
162
|
+
|
163
|
+
# pylint: enable=g-importing-member
|
164
|
+
# pylint: enable=g-bad-import-order
|
@@ -14,75 +14,11 @@
|
|
14
14
|
"""Common traits for Python objects.
|
15
15
|
|
16
16
|
This file defines interfaces for describing the common traits of a Python
|
17
|
-
object, for example, partiality (MaybePartial),
|
18
|
-
functor (Functor).
|
17
|
+
object, for example, partiality (MaybePartial), functor (Functor).
|
19
18
|
"""
|
20
19
|
|
21
20
|
import abc
|
22
|
-
from typing import Any,
|
23
|
-
from pyglove.core.object_utils import thread_local
|
24
|
-
|
25
|
-
|
26
|
-
_TLS_STR_FORMAT_KWARGS = '_str_format_kwargs'
|
27
|
-
_TLS_REPR_FORMAT_KWARGS = '_repr_format_kwargs'
|
28
|
-
|
29
|
-
|
30
|
-
def str_format(**kwargs) -> ContextManager[Dict[str, Any]]:
|
31
|
-
"""Context manager for setting the default format kwargs for __str__."""
|
32
|
-
return thread_local.thread_local_arg_scope(_TLS_STR_FORMAT_KWARGS, **kwargs)
|
33
|
-
|
34
|
-
|
35
|
-
def repr_format(**kwargs) -> ContextManager[Dict[str, Any]]:
|
36
|
-
"""Context manager for setting the default format kwargs for __repr__."""
|
37
|
-
return thread_local.thread_local_arg_scope(_TLS_REPR_FORMAT_KWARGS, **kwargs)
|
38
|
-
|
39
|
-
|
40
|
-
class Formattable(metaclass=abc.ABCMeta):
|
41
|
-
"""Interface for classes whose instances can be pretty-formatted.
|
42
|
-
|
43
|
-
This interface overrides the default ``__repr__`` and ``__str__`` method, thus
|
44
|
-
all ``Formattable`` objects can be printed nicely.
|
45
|
-
|
46
|
-
All symbolic types implement this interface.
|
47
|
-
"""
|
48
|
-
|
49
|
-
# Additional format keyword arguments for `__str__`.
|
50
|
-
__str_format_kwargs__ = dict(compact=False, verbose=True)
|
51
|
-
|
52
|
-
# Additional format keyword arguments for `__repr__`.
|
53
|
-
__repr_format_kwargs__ = dict(compact=True)
|
54
|
-
|
55
|
-
@abc.abstractmethod
|
56
|
-
def format(self,
|
57
|
-
compact: bool = False,
|
58
|
-
verbose: bool = True,
|
59
|
-
root_indent: int = 0,
|
60
|
-
**kwargs) -> str:
|
61
|
-
"""Formats this object into a string representation.
|
62
|
-
|
63
|
-
Args:
|
64
|
-
compact: If True, this object will be formatted into a single line.
|
65
|
-
verbose: If True, this object will be formatted with verbosity.
|
66
|
-
Subclasses should define `verbosity` on their own.
|
67
|
-
root_indent: The start indent level for this object if the output is a
|
68
|
-
multi-line string.
|
69
|
-
**kwargs: Subclass specific keyword arguments.
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
A string of formatted object.
|
73
|
-
"""
|
74
|
-
|
75
|
-
def __str__(self) -> str:
|
76
|
-
"""Returns the full (maybe multi-line) representation of this object."""
|
77
|
-
kwargs = dict(self.__str_format_kwargs__)
|
78
|
-
kwargs.update(thread_local.thread_local_kwargs(_TLS_STR_FORMAT_KWARGS))
|
79
|
-
return self.format(**kwargs)
|
80
|
-
|
81
|
-
def __repr__(self) -> str:
|
82
|
-
"""Returns a single-line representation of this object."""
|
83
|
-
kwargs = dict(self.__repr_format_kwargs__)
|
84
|
-
kwargs.update(thread_local.thread_local_kwargs(_TLS_REPR_FORMAT_KWARGS))
|
85
|
-
return self.format(**kwargs)
|
21
|
+
from typing import Any, Dict, Optional, Union
|
86
22
|
|
87
23
|
|
88
24
|
class MaybePartial(metaclass=abc.ABCMeta):
|
@@ -111,7 +47,7 @@ class MaybePartial(metaclass=abc.ABCMeta):
|
|
111
47
|
return len(self.missing_values()) > 0 # pylint: disable=g-explicit-length-test
|
112
48
|
|
113
49
|
@abc.abstractmethod
|
114
|
-
def missing_values(self, flatten: bool = True) -> Dict[str, Any]: # pylint: disable=redefined-outer-name
|
50
|
+
def missing_values(self, flatten: bool = True) -> Dict[Union[str, int], Any]: # pylint: disable=redefined-outer-name
|
115
51
|
"""Returns missing values from this object.
|
116
52
|
|
117
53
|
Args:
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2022 The PyGlove Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
import unittest
|
15
|
+
from pyglove.core.utils import common_traits
|
16
|
+
|
17
|
+
|
18
|
+
class ExplicitlyOverrideTest(unittest.TestCase):
|
19
|
+
|
20
|
+
def test_explicitly_override(self):
|
21
|
+
class A:
|
22
|
+
|
23
|
+
@common_traits.explicit_method_override
|
24
|
+
def __init__(self, x, y):
|
25
|
+
pass
|
26
|
+
|
27
|
+
def bar(self):
|
28
|
+
pass
|
29
|
+
|
30
|
+
common_traits.ensure_explicit_method_override(A.__init__)
|
31
|
+
with self.assertRaisesRegex(TypeError, '.* is a PyGlove managed method'):
|
32
|
+
common_traits.ensure_explicit_method_override(A.bar)
|
33
|
+
|
34
|
+
|
35
|
+
if __name__ == '__main__':
|
36
|
+
unittest.main()
|
@@ -15,6 +15,7 @@
|
|
15
15
|
|
16
16
|
import dataclasses
|
17
17
|
import enum
|
18
|
+
import inspect
|
18
19
|
from typing import Any, Dict, List, Optional
|
19
20
|
|
20
21
|
import docstring_parser
|
@@ -74,6 +75,28 @@ class DocStr:
|
|
74
75
|
raises: List[DocStrRaises]
|
75
76
|
blank_after_short_description: bool = True
|
76
77
|
|
78
|
+
@property
|
79
|
+
def description(self) -> Optional[str]:
|
80
|
+
"""Returns short_description + long_description."""
|
81
|
+
if self.short_description is None and self.long_description is None:
|
82
|
+
return None
|
83
|
+
description = self.short_description or ''
|
84
|
+
if self.blank_after_short_description:
|
85
|
+
description += '\n'
|
86
|
+
if self.long_description:
|
87
|
+
description += '\n' + self.long_description
|
88
|
+
return description.rstrip('\n')
|
89
|
+
|
90
|
+
def parameter(self, param: inspect.Parameter) -> Optional[DocStrArgument]:
|
91
|
+
"""Returns doc str for an inspected parameter."""
|
92
|
+
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
93
|
+
name = f'*{param.name}'
|
94
|
+
elif param.kind == inspect.Parameter.VAR_KEYWORD:
|
95
|
+
name = f'**{param.name}'
|
96
|
+
else:
|
97
|
+
name = param.name
|
98
|
+
return self.args.get(name)
|
99
|
+
|
77
100
|
@classmethod
|
78
101
|
def parse(cls, text: str, style: Optional[DocStrStyle] = None) -> 'DocStr':
|
79
102
|
"""Parses a docstring."""
|
@@ -11,10 +11,9 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
|
15
|
-
|
14
|
+
import inspect
|
16
15
|
import unittest
|
17
|
-
from pyglove.core.
|
16
|
+
from pyglove.core.utils import docstr_utils
|
18
17
|
|
19
18
|
|
20
19
|
class DocStrTest(unittest.TestCase):
|
@@ -57,7 +56,16 @@ class DocStrTest(unittest.TestCase):
|
|
57
56
|
del args, kwargs
|
58
57
|
return x + y
|
59
58
|
|
60
|
-
|
59
|
+
docstr = docstr_utils.docstr(my_sum)
|
60
|
+
self.assertEqual(
|
61
|
+
docstr.description,
|
62
|
+
inspect.cleandoc('''
|
63
|
+
Returns the sum of two integers.
|
64
|
+
|
65
|
+
This function will return the sum of two integers.
|
66
|
+
''')
|
67
|
+
)
|
68
|
+
self.assertEqual(docstr, docstr_utils.DocStr(
|
61
69
|
style=docstr_utils.DocStrStyle.GOOGLE,
|
62
70
|
short_description='Returns the sum of two integers.',
|
63
71
|
long_description='This function will return the sum of two integers.',
|
@@ -93,6 +101,11 @@ class DocStrTest(unittest.TestCase):
|
|
93
101
|
)
|
94
102
|
]
|
95
103
|
))
|
104
|
+
sig = inspect.signature(my_sum)
|
105
|
+
self.assertIsNotNone(docstr.parameter(sig.parameters['x']))
|
106
|
+
self.assertIsNotNone(docstr.parameter(sig.parameters['y']))
|
107
|
+
self.assertIsNotNone(docstr.parameter(sig.parameters['args']))
|
108
|
+
self.assertIsNotNone(docstr.parameter(sig.parameters['kwargs']))
|
96
109
|
|
97
110
|
class Foo:
|
98
111
|
pass
|
@@ -100,6 +113,25 @@ class DocStrTest(unittest.TestCase):
|
|
100
113
|
self.assertIsNone(docstr_utils.docstr(Foo))
|
101
114
|
self.assertIsNone(docstr_utils.docstr(None))
|
102
115
|
|
116
|
+
class Bar:
|
117
|
+
"""bar."""
|
118
|
+
|
119
|
+
self.assertEqual(docstr_utils.docstr(Bar).description, 'bar.')
|
120
|
+
|
121
|
+
# pylint: disable=g-classes-have-attributes
|
122
|
+
# pylint: disable=g-short-docstring-punctuation
|
123
|
+
# pylint: disable=g-space-before-docstring-summary
|
124
|
+
# pylint: disable=g-no-space-after-docstring-summary
|
125
|
+
class Baz:
|
126
|
+
"""
|
127
|
+
Args:
|
128
|
+
x: int
|
129
|
+
"""
|
130
|
+
# pylint: enable=g-space-before-docstring-summary
|
131
|
+
# pylint: enable=g-short-docstring-punctuation
|
132
|
+
# pylint: enable=g-classes-have-attributes
|
133
|
+
self.assertIsNone(docstr_utils.docstr(Baz).description)
|
134
|
+
|
103
135
|
|
104
136
|
if __name__ == '__main__':
|
105
137
|
unittest.main()
|
@@ -17,28 +17,92 @@ import contextlib
|
|
17
17
|
import dataclasses
|
18
18
|
import inspect
|
19
19
|
import re
|
20
|
-
|
20
|
+
import sys
|
21
|
+
import traceback
|
22
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
|
23
|
+
|
24
|
+
from pyglove.core.utils import formatting
|
25
|
+
from pyglove.core.utils import json_conversion
|
26
|
+
|
27
|
+
|
28
|
+
@dataclasses.dataclass(frozen=True)
|
29
|
+
class ErrorInfo(json_conversion.JSONConvertible, formatting.Formattable):
|
30
|
+
"""Serializable error information.
|
31
|
+
|
32
|
+
Attributes:
|
33
|
+
tag: A path of the error types in the exception chain. For example,
|
34
|
+
`ValueError.ZeroDivisionError` means the error is a `ZeroDivisionError`
|
35
|
+
raised at the first place and then reraised as a `ValueError`.
|
36
|
+
description: The description of the error.
|
37
|
+
stacktrace: The stacktrace of the error.
|
38
|
+
"""
|
39
|
+
|
40
|
+
tag: str
|
41
|
+
description: str
|
42
|
+
stacktrace: str
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def _compute_tag(cls, error: BaseException):
|
46
|
+
error_types = []
|
47
|
+
while error is not None:
|
48
|
+
error_types.append(error.__class__.__name__)
|
49
|
+
error = getattr(error, 'cause', error.__cause__)
|
50
|
+
return '.'.join(error_types)
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def from_exception(cls, error: BaseException) -> 'ErrorInfo':
|
54
|
+
"""Creates an error info from an exception."""
|
55
|
+
return cls(
|
56
|
+
tag=cls._compute_tag(error),
|
57
|
+
description=str(error),
|
58
|
+
stacktrace=''.join(
|
59
|
+
traceback.format_exception(*sys.exc_info())
|
60
|
+
)
|
61
|
+
)
|
62
|
+
|
63
|
+
def to_json(self, **kwargs) -> Dict[str, Any]:
|
64
|
+
return self.to_json_dict(
|
65
|
+
fields=dict(
|
66
|
+
tag=(self.tag, None),
|
67
|
+
description=(self.description, None),
|
68
|
+
stacktrace=(self.stacktrace, None),
|
69
|
+
),
|
70
|
+
exclude_default=True,
|
71
|
+
**kwargs,
|
72
|
+
)
|
73
|
+
|
74
|
+
def format(self, *args, **kwargs) -> str:
|
75
|
+
return formatting.kvlist_str(
|
76
|
+
[
|
77
|
+
('tag', self.tag, None),
|
78
|
+
('description', self.description, None),
|
79
|
+
('stacktrace', self.stacktrace, None),
|
80
|
+
],
|
81
|
+
*args,
|
82
|
+
label=self.__class__.__name__,
|
83
|
+
**kwargs,
|
84
|
+
)
|
21
85
|
|
22
86
|
|
23
87
|
@dataclasses.dataclass()
|
24
88
|
class CatchErrorsContext:
|
25
89
|
"""Context for pg.catch_errors."""
|
26
|
-
error: Optional[
|
90
|
+
error: Optional[BaseException] = None
|
27
91
|
|
28
92
|
|
29
93
|
@contextlib.contextmanager
|
30
94
|
def catch_errors(
|
31
95
|
errors: Union[
|
32
|
-
Union[Type[
|
33
|
-
Sequence[Union[Type[
|
96
|
+
Union[Type[BaseException], Tuple[Type[BaseException], str]],
|
97
|
+
Sequence[Union[Type[BaseException], Tuple[Type[BaseException], str]]],
|
34
98
|
],
|
35
|
-
error_handler: Optional[Callable[[
|
99
|
+
error_handler: Optional[Callable[[BaseException], None]] = None
|
36
100
|
):
|
37
101
|
"""Context manager for catching user-specified exceptions.
|
38
102
|
|
39
103
|
Examples::
|
40
104
|
|
41
|
-
with pg.
|
105
|
+
with pg.utils.catch_errors(
|
42
106
|
[
|
43
107
|
RuntimeErrror,
|
44
108
|
(ValueError, 'Input is wrong.')
|
@@ -71,7 +135,7 @@ def catch_errors(
|
|
71
135
|
):
|
72
136
|
errors = [errors]
|
73
137
|
|
74
|
-
error_mapping: Dict[Type[
|
138
|
+
error_mapping: Dict[Type[BaseException], List[str]] = {}
|
75
139
|
for error_type in errors:
|
76
140
|
regex = None
|
77
141
|
if isinstance(error_type, tuple):
|
@@ -82,7 +146,9 @@ def catch_errors(
|
|
82
146
|
f'to match. Encountered: {error_type!r}.'
|
83
147
|
)
|
84
148
|
error_type, regex = error_type
|
85
|
-
if not (
|
149
|
+
if not (
|
150
|
+
inspect.isclass(error_type) and issubclass(error_type, BaseException)
|
151
|
+
):
|
86
152
|
raise TypeError(f'Exception contains non-except types: {error_type!r}.')
|
87
153
|
if error_type not in error_mapping:
|
88
154
|
error_mapping[error_type] = []
|
@@ -93,7 +159,7 @@ def catch_errors(
|
|
93
159
|
try:
|
94
160
|
yield context
|
95
161
|
except tuple(error_mapping.keys()) as e:
|
96
|
-
error_message = str(e)
|
162
|
+
error_message = e.__class__.__name__ + ': ' + str(e)
|
97
163
|
found_match = False
|
98
164
|
for error_type, error_regexes in error_mapping.items():
|
99
165
|
if isinstance(e, error_type):
|
@@ -101,6 +167,9 @@ def catch_errors(
|
|
101
167
|
found_match = True
|
102
168
|
else:
|
103
169
|
for regex in error_regexes:
|
170
|
+
assert regex is not None
|
171
|
+
if not regex.startswith(('^', '.*')):
|
172
|
+
regex = '.*' + regex
|
104
173
|
if re.match(regex, error_message):
|
105
174
|
found_match = True
|
106
175
|
break
|
@@ -11,10 +11,64 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
|
15
|
-
|
14
|
+
import inspect
|
16
15
|
import unittest
|
17
|
-
from pyglove.core.
|
16
|
+
from pyglove.core.utils import error_utils
|
17
|
+
|
18
|
+
|
19
|
+
class ErrorInfoTest(unittest.TestCase):
|
20
|
+
"""Tests for ErrorInfo."""
|
21
|
+
|
22
|
+
def test_from_exception(self):
|
23
|
+
|
24
|
+
def foo():
|
25
|
+
return 1 / 0
|
26
|
+
|
27
|
+
def bar():
|
28
|
+
try:
|
29
|
+
foo()
|
30
|
+
except ZeroDivisionError as e:
|
31
|
+
raise ValueError('Bad call to `foo`') from e
|
32
|
+
|
33
|
+
error_info = None
|
34
|
+
try:
|
35
|
+
bar()
|
36
|
+
except ValueError as e:
|
37
|
+
error_info = error_utils.ErrorInfo.from_exception(e)
|
38
|
+
self.assertIsNotNone(error_info)
|
39
|
+
self.assertEqual(error_info.tag, 'ValueError.ZeroDivisionError')
|
40
|
+
self.assertEqual(error_info.description, 'Bad call to `foo`')
|
41
|
+
self.assertIn('Traceback (most recent call last)', error_info.stacktrace)
|
42
|
+
|
43
|
+
def test_to_json(self):
|
44
|
+
error_info = error_utils.ErrorInfo(
|
45
|
+
tag='ValueError.ZeroDivisionError',
|
46
|
+
description='Bad call to `foo`',
|
47
|
+
stacktrace='Traceback (most recent call last)',
|
48
|
+
)
|
49
|
+
json_dict = error_info.to_json()
|
50
|
+
error_info2 = error_utils.ErrorInfo.from_json(json_dict)
|
51
|
+
self.assertIsNot(error_info2, error_info)
|
52
|
+
self.assertEqual(error_info2, error_info)
|
53
|
+
|
54
|
+
def test_format(self):
|
55
|
+
error_info = error_utils.ErrorInfo(
|
56
|
+
tag='ValueError.ZeroDivisionError',
|
57
|
+
description='Bad call to `foo`',
|
58
|
+
stacktrace='Traceback (most recent call last)',
|
59
|
+
)
|
60
|
+
self.assertEqual(
|
61
|
+
error_info.format(compact=False),
|
62
|
+
inspect.cleandoc(
|
63
|
+
"""
|
64
|
+
ErrorInfo(
|
65
|
+
tag='ValueError.ZeroDivisionError',
|
66
|
+
description='Bad call to `foo`',
|
67
|
+
stacktrace='Traceback (most recent call last)'
|
68
|
+
)
|
69
|
+
"""
|
70
|
+
)
|
71
|
+
)
|
18
72
|
|
19
73
|
|
20
74
|
class CatchErrorsTest(unittest.TestCase):
|
@@ -36,11 +90,13 @@ class CatchErrorsTest(unittest.TestCase):
|
|
36
90
|
|
37
91
|
self.assert_caught_error(foo, ValueError)
|
38
92
|
self.assert_caught_error(foo, (ValueError,))
|
93
|
+
self.assert_caught_error(foo, (Exception, 'ValueError'))
|
39
94
|
self.assert_caught_error(foo, (KeyError, ValueError))
|
40
|
-
self.assert_caught_error(foo, (ValueError, '
|
41
|
-
self.assert_caught_error(foo, (KeyError, (ValueError, '
|
95
|
+
self.assert_caught_error(foo, (ValueError, 'an error'))
|
96
|
+
self.assert_caught_error(foo, (KeyError, (ValueError, 'an error'),))
|
42
97
|
|
43
98
|
self.assert_propagate_error(foo, KeyError)
|
99
|
+
self.assert_propagate_error(foo, (ValueError, '^an error'))
|
44
100
|
self.assert_propagate_error(foo, (ValueError, 'something else'))
|
45
101
|
|
46
102
|
def test_catch_errors_with_error_handler(self):
|