Skip to content

Commit 41c6ff1

Browse files
committed
Test to_hash_string;
Rearrange and refactor code;
1 parent 3a845c3 commit 41c6ff1

File tree

5 files changed

+57
-27
lines changed

5 files changed

+57
-27
lines changed

rest_client_gen/dynamic_typing/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .base import (
2-
BaseType, ImportPathList, MetaData, NoneType, Unknown, UnknownType
2+
BaseType, ImportPathList, MetaData, NoneType, Unknown, UnknownType, get_hash_string
33
)
44
from .complex import ComplexType, DList, DOptional, DTuple, DUnion, SingleType
55
from .models_meta import AbsoluteModelRef, ModelMeta, ModelPtr

rest_client_gen/dynamic_typing/base.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from inspect import isclass
12
from typing import Iterable, List, Tuple, Union
23

34
ImportPathList = List[Tuple[str, Union[Iterable[str], str, None]]]
@@ -31,10 +32,22 @@ def to_typing_code(self) -> Tuple[ImportPathList, str]:
3132
def to_hash_string(self) -> str:
3233
"""
3334
Return unique string that can be used to generate hash of type instance.
35+
Caches hash value by default. If subclass can mutate (by default it always can)
36+
then it should define setters to safely invalidate cached value.
3437
3538
:return: hash string
3639
"""
37-
# NOTE: Do not override __hash__ function as BaseType instances could mutate
40+
# NOTE: Do not override __hash__ function because BaseType instances isn't immutable
41+
if not self._hash:
42+
self._hash = self._to_hash_string()
43+
return self._hash
44+
45+
def _to_hash_string(self) -> str:
46+
"""
47+
Hash getter method to override
48+
49+
:return:
50+
"""
3851
raise NotImplementedError()
3952

4053

@@ -60,3 +73,12 @@ def to_hash_string(self) -> str:
6073
Unknown = UnknownType()
6174
NoneType = type(None)
6275
MetaData = Union[type, dict, BaseType]
76+
77+
78+
def get_hash_string(t: MetaData):
79+
if isinstance(t, dict):
80+
return str(hash(tuple((k, get_hash_string(v)) for k, v in t.items())))
81+
elif isclass(t):
82+
return str(t)
83+
elif isinstance(t, BaseType):
84+
return t.to_hash_string()

rest_client_gen/dynamic_typing/complex.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
from inspect import isclass
21
from itertools import chain
32
from typing import Iterable, List, Tuple, Union
43

5-
from .base import BaseType, ImportPathList, MetaData
4+
from .base import BaseType, ImportPathList, MetaData, get_hash_string
65
from .typing import metadata_to_typing
76

87

9-
def get_hash_string(t: MetaData):
10-
if isinstance(t, dict):
11-
return str(hash(tuple((k, get_hash_string(v)) for k, v in t.items())))
12-
elif isclass(t):
13-
return str(t)
14-
elif isinstance(t, BaseType):
15-
return t.to_hash_string()
16-
17-
188
class SingleType(BaseType):
19-
__slots__ = ["_type"]
9+
__slots__ = ["_type", "_hash"]
2010

2111
def __init__(self, t: MetaData):
2212
self._type = t
@@ -47,14 +37,12 @@ def replace(self, t: 'MetaData', **kwargs) -> 'SingleType':
4737
self.type = t
4838
return self
4939

50-
def to_hash_string(self) -> str:
51-
if not self._hash:
52-
self._hash = f"{type(self).__name__}/{get_hash_string(self.type)}"
53-
return self._hash
40+
def _to_hash_string(self) -> str:
41+
return f"{type(self).__name__}/{get_hash_string(self.type)}"
5442

5543

5644
class ComplexType(BaseType):
57-
__slots__ = ["_types"]
45+
__slots__ = ["_types", "_sorted", "_hash"]
5846

5947
def __init__(self, *types: MetaData):
6048
self._types = list(types)
@@ -125,10 +113,8 @@ def to_typing_code(self) -> Tuple[ImportPathList, str]:
125113
f"[{nested}]"
126114
)
127115

128-
def to_hash_string(self) -> str:
129-
if not self._hash:
130-
self._hash = type(self).__name__ + "/" + ",".join(map(get_hash_string, self.types))
131-
return self._hash
116+
def _to_hash_string(self) -> str:
117+
return type(self).__name__ + "/" + ",".join(map(get_hash_string, self.types))
132118

133119

134120
class DOptional(SingleType):

rest_client_gen/dynamic_typing/models_meta.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ def replace(self, t: ModelMeta, **kwargs) -> 'ModelPtr':
121121
return self
122122

123123
def replace_parent(self, t: ModelMeta, **kwargs) -> 'ModelPtr':
124+
self._hash = None
124125
self.parent.remove_child_ref(self)
125126
self.parent = t
126127
self.parent.add_child_ref(self)
@@ -129,7 +130,7 @@ def replace_parent(self, t: ModelMeta, **kwargs) -> 'ModelPtr':
129130
def to_typing_code(self) -> Tuple[ImportPathList, str]:
130131
return AbsoluteModelRef(self.type).to_typing_code()
131132

132-
def to_hash_string(self) -> str:
133+
def _to_hash_string(self) -> str:
133134
return f"{type(self).__name__}_#{self.type.index}"
134135

135136

@@ -153,6 +154,11 @@ class NestedModel:
153154
This information is only available at the models code generation stage
154155
while typing code is generated from raw metadata and passing this absolute path as argument
155156
to each ModelPtr would be annoying.
157+
158+
Usage:
159+
160+
with AbsoluteModelRef.inject({TestModel: "ParentModelName"}):
161+
<some code generation>
156162
"""
157163

158164
class Context:

test/test_dynamic_typing/test_dynamic_typing.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from builtins import complex
2+
13
import pytest
24

3-
from rest_client_gen.dynamic_typing import DUnion
5+
from rest_client_gen.dynamic_typing import DUnion, get_hash_string
46

57
# *args | MetaData
6-
test_dunion= [
8+
test_dunion = [
79
pytest.param(
810
[int, int],
911
DUnion(int),
@@ -21,7 +23,21 @@
2123
)
2224
]
2325

26+
2427
@pytest.mark.parametrize("value,expected", test_dunion)
2528
def test_dunion_creation(value, expected):
2629
result = DUnion(*value)
27-
assert result == expected
30+
assert result == expected
31+
32+
33+
def test_hash_string():
34+
a = {'a': int}
35+
b = {'b': int}
36+
c = {'a': float}
37+
assert len(set(map(get_hash_string, (a, b, c)))) == 3
38+
39+
union = DUnion(str, float)
40+
h1 = union.to_hash_string()
41+
union.replace(complex, index=0)
42+
h2 = union.to_hash_string()
43+
assert h1 != h2, f"{h1}, {h2}"

0 commit comments

Comments
 (0)