pyglove 0.4.5.dev202502020807__py3-none-any.whl → 0.4.5.dev202502040809__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.
@@ -30,6 +30,11 @@ def is_subclass(
30
30
  """An issubclass extension that supports Any and generic types."""
31
31
 
32
32
  def _is_subclass(src: Type[Any], target: Type[Any]) -> bool:
33
+ if is_protocol(target):
34
+ # NOTE(daiyip): loose runtime check for Protocol.
35
+ # As runtime protocol check (through decorating the protocol class with
36
+ # @typing.runtime_checkable) might be unreliable and very slow.
37
+ return inspect.isclass(src) and src.__module__ != 'builtins'
33
38
  if target is Any:
34
39
  return True
35
40
  elif src is Any:
@@ -78,6 +83,17 @@ def is_subclass(
78
83
  return _is_subclass(src, target)
79
84
 
80
85
 
86
+ def is_protocol(maybe_protocol: Type[Any]) -> bool:
87
+ """Returns True if a type is a protocol."""
88
+ if not inspect.isclass(maybe_protocol):
89
+ return False
90
+ maybe_protocol = typing.get_origin(maybe_protocol) or maybe_protocol
91
+ for base in maybe_protocol.__bases__:
92
+ if base is typing.Protocol or typing.get_origin(base) is typing.Protocol:
93
+ return True
94
+ return False
95
+
96
+
81
97
  def is_generic(maybe_generic: Type[Any]) -> bool:
82
98
  """Returns True if a type is a generic class."""
83
99
  return typing.get_origin(maybe_generic) is not None
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
  """Tests for generic type utility."""
15
15
 
16
- from typing import Any, Generic, TypeVar
16
+ from typing import Any, Generic, Protocol, TypeVar
17
17
  import unittest
18
18
 
19
19
  from pyglove.core.typing import callable_signature
@@ -62,6 +62,26 @@ class AA1(AA):
62
62
  pass
63
63
 
64
64
 
65
+ class E(Protocol):
66
+
67
+ def __call__(self, x: int) -> int:
68
+ pass
69
+
70
+
71
+ class E1(E):
72
+ pass
73
+
74
+
75
+ class E2(E, Protocol):
76
+
77
+ def bar(self, x: int) -> int:
78
+ pass
79
+
80
+
81
+ class F(Protocol[XType]):
82
+ x: XType
83
+
84
+
65
85
  class InspectTest(unittest.TestCase):
66
86
 
67
87
  def test_issubclass(self):
@@ -111,6 +131,16 @@ class InspectTest(unittest.TestCase):
111
131
  self.assertFalse(inspect.is_subclass(B1[Str], B[str]))
112
132
  self.assertFalse(inspect.is_subclass(B1[Str], A[str, int]))
113
133
 
134
+ # Protocol check.
135
+ self.assertTrue(inspect.is_subclass(E, E))
136
+ self.assertTrue(inspect.is_subclass(E, Protocol))
137
+ # We do not really check protocol comformance at runtime for performance
138
+ # reasons. So all user classes are considered as subclasses of a Protocol
139
+ # class.
140
+ self.assertTrue(inspect.is_subclass(A, E))
141
+ self.assertFalse(inspect.is_subclass(str, E))
142
+ self.assertFalse(inspect.is_subclass(1, E))
143
+
114
144
  # Test tuple cases.
115
145
  self.assertTrue(inspect.is_subclass(int, (str, int)))
116
146
  self.assertTrue(inspect.is_subclass(C, (int, A[str, int])))
@@ -124,7 +154,24 @@ class InspectTest(unittest.TestCase):
124
154
  self.assertTrue(inspect.is_instance(D(), A[str, int]))
125
155
  self.assertTrue(inspect.is_instance(D(), B[str]))
126
156
 
157
+ def test_is_protocol(self):
158
+ self.assertFalse(inspect.is_protocol(1))
159
+ self.assertFalse(inspect.is_protocol(str))
160
+ self.assertFalse(inspect.is_protocol(Any))
161
+ self.assertFalse(inspect.is_protocol(A))
162
+ self.assertFalse(inspect.is_protocol(A[str, int]))
163
+ self.assertFalse(inspect.is_protocol(Protocol))
164
+ self.assertFalse(inspect.is_protocol(Protocol[XType]))
165
+ self.assertTrue(inspect.is_protocol(E))
166
+ # subclasses of a Protocol class will downgrade if it's not explicitly
167
+ # inherited from Protocol.
168
+ # See https://typing.readthedocs.io/en/latest/spec/protocol.html
169
+ self.assertFalse(inspect.is_protocol(E1))
170
+ self.assertTrue(inspect.is_protocol(E2))
171
+ self.assertTrue(inspect.is_protocol(F))
172
+
127
173
  def test_is_generic(self):
174
+ self.assertFalse(inspect.is_generic(1))
128
175
  self.assertFalse(inspect.is_generic(str))
129
176
  self.assertFalse(inspect.is_generic(Any))
130
177
  self.assertFalse(inspect.is_generic(A))
@@ -24,11 +24,13 @@ from pyglove.core.typing import inspect as pg_inspect
24
24
  class _TypeConverterRegistry:
25
25
  """Type converter registry."""
26
26
 
27
+ _JSON_VALUE_TYPES = frozenset(
28
+ [int, float, bool, type(None), list, tuple, dict, str]
29
+ )
30
+
27
31
  def __init__(self):
28
32
  """Constructor."""
29
33
  self._converter_list = []
30
- self._json_value_types = set(
31
- [int, float, bool, type(None), list, tuple, dict, str])
32
34
 
33
35
  def register(
34
36
  self,
@@ -45,14 +47,17 @@ class _TypeConverterRegistry:
45
47
  raise TypeError('Argument \'src\' and \'dest\' must be a type or '
46
48
  'tuple of types.')
47
49
  if isinstance(dest, tuple):
48
- json_value_convertible = any(d in self._json_value_types for d in dest)
50
+ json_value_convertible = any(d in self._JSON_VALUE_TYPES for d in dest)
49
51
  else:
50
- json_value_convertible = dest in self._json_value_types
52
+ json_value_convertible = dest in self._JSON_VALUE_TYPES
51
53
  self._converter_list.append((src, dest, convert_fn, json_value_convertible))
52
54
 
53
55
  def get_converter(
54
56
  self, src: Type[Any], dest: Type[Any]) -> Optional[Callable[[Any], Any]]:
55
57
  """Get converter from source type to destination type."""
58
+ if pg_inspect.is_protocol(dest):
59
+ return None
60
+
56
61
  # TODO(daiyip): Right now we don't see the need of a large number of
57
62
  # converters, thus its affordable to iterate the list.
58
63
  # We may consider more efficient way to do lookup in future.
@@ -38,6 +38,10 @@ class TypeConversionTest(unittest.TestCase):
38
38
  super().__init__(x)
39
39
  self.y = y
40
40
 
41
+ class C(typing.Protocol):
42
+ def foo(self, x: int) -> int:
43
+ pass
44
+
41
45
  a_converter = lambda a: a.x
42
46
  type_conversion.register_converter(A, (str, int), a_converter)
43
47
 
@@ -46,6 +50,7 @@ class TypeConversionTest(unittest.TestCase):
46
50
  self.assertIs(type_conversion.get_json_value_converter(A), a_converter)
47
51
 
48
52
  self.assertIsNone(type_conversion.get_converter(A, (float, bool)))
53
+ self.assertIsNone(type_conversion.get_converter(A, C))
49
54
  self.assertIs(type_conversion.get_converter(A, (float, int)), a_converter)
50
55
 
51
56
  # B is a subclass of A, so A's converter applies.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pyglove
3
- Version: 0.4.5.dev202502020807
3
+ Version: 0.4.5.dev202502040809
4
4
  Summary: PyGlove: A library for manipulating Python objects.
5
5
  Home-page: https://github.com/google/pyglove
6
6
  Author: PyGlove Authors
@@ -118,13 +118,13 @@ pyglove/core/typing/callable_signature_test.py,sha256=iQmHsKPhJPQlMikDhEyxKyq7yW
118
118
  pyglove/core/typing/class_schema.py,sha256=xfPrLYpqntejMLvSL-7rNiXHTQOgAr2kmxWeR890qLs,53940
119
119
  pyglove/core/typing/class_schema_test.py,sha256=UWANPqhu9v_FHNo3cVe05P-bO-HliBmrSBywKrlWep0,29204
120
120
  pyglove/core/typing/custom_typing.py,sha256=qdnIKHWNt5kZAAFdpQXra8bBu6RljMbbJ_YDG2mhAUA,2205
121
- pyglove/core/typing/inspect.py,sha256=Tp2rYqRvMFvL0NzkJo4p04_hQSF__Eip8RduhEaSFfE,7046
122
- pyglove/core/typing/inspect_test.py,sha256=vJ-MZvSPjzzA7sDY6XIeMeX_aob_6VjGQcIIn_MJl64,8826
121
+ pyglove/core/typing/inspect.py,sha256=VLSz1KAunNm2hx0eEMjiwxKLl9FHlKr9nHelLT25iEA,7726
122
+ pyglove/core/typing/inspect_test.py,sha256=xclevobF0X8c_B5b1q1dkBJZN1TsVA1RUhk5l25DUCM,10248
123
123
  pyglove/core/typing/key_specs.py,sha256=-7xjCuUGoQgD0sMafsRFNlw3S4f1r-7t5OO4ev5bbeI,9225
124
124
  pyglove/core/typing/key_specs_test.py,sha256=5zornpyHMAYoRaG8KDXHiQ3obu9UfRp3399lBeUNTPk,6499
125
125
  pyglove/core/typing/pytype_support.py,sha256=lyX11WVbCwoOi5tTQ90pEOS-yvo_6iEi7Lxbp-nXu2A,2069
126
- pyglove/core/typing/type_conversion.py,sha256=6diZrxNsvSyTLLNdoCqcWatWmhh7kZOiKCesyg-nEYQ,5167
127
- pyglove/core/typing/type_conversion_test.py,sha256=qwNeotCb96vdUELVtA-0Ee19Jm4yOnPZuSIz5nyEbGo,5203
126
+ pyglove/core/typing/type_conversion.py,sha256=0L4Cbsw_QiM-gpsn-4y-XLEIvwiUB16Clj9gCtoC_Xc,5224
127
+ pyglove/core/typing/type_conversion_test.py,sha256=BhASOGvtKXmYLWKCELU1RVB_Nmt1V-saSkGogvsNL7E,5342
128
128
  pyglove/core/typing/typed_missing.py,sha256=-l1omAu0jBZv5BnsFYXBqfvQwVBnmPh_X1wcIKD9bOk,2734
129
129
  pyglove/core/typing/typed_missing_test.py,sha256=TCNsb1SRpFaVdxYn2mB_yaLuja8w5Qn5NP7uGiZVBWs,2301
130
130
  pyglove/core/typing/value_specs.py,sha256=Yxdrz4aURMBrtqUW4to-2Vptc6Z6oqQrLyMBiAZt2bc,102302
@@ -211,8 +211,8 @@ pyglove/ext/scalars/randoms.py,sha256=LkMIIx7lOq_lvJvVS3BrgWGuWl7Pi91-lA-O8x_gZs
211
211
  pyglove/ext/scalars/randoms_test.py,sha256=nEhiqarg8l_5EOucp59CYrpO2uKxS1pe0hmBdZUzRNM,2000
212
212
  pyglove/ext/scalars/step_wise.py,sha256=IDw3tuTpv0KVh7AN44W43zqm1-E0HWPUlytWOQC9w3Y,3789
213
213
  pyglove/ext/scalars/step_wise_test.py,sha256=TL1vJ19xVx2t5HKuyIzGoogF7N3Rm8YhLE6JF7i0iy8,2540
214
- pyglove-0.4.5.dev202502020807.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
215
- pyglove-0.4.5.dev202502020807.dist-info/METADATA,sha256=GrPVTap7aw6_4KTpGyqoYfIm6bsCSdrlYm4-ODrgUxs,7067
216
- pyglove-0.4.5.dev202502020807.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
217
- pyglove-0.4.5.dev202502020807.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
218
- pyglove-0.4.5.dev202502020807.dist-info/RECORD,,
214
+ pyglove-0.4.5.dev202502040809.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
215
+ pyglove-0.4.5.dev202502040809.dist-info/METADATA,sha256=g47xUh8StCt5ojg7Arw96L0nds0Kl8hrGazZx7IsU10,7067
216
+ pyglove-0.4.5.dev202502040809.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
217
+ pyglove-0.4.5.dev202502040809.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
218
+ pyglove-0.4.5.dev202502040809.dist-info/RECORD,,