Skip to content

Commit af305f4

Browse files
authored
Merge pull request #273 from ncilfone/bugs_v3.0.0
Bug Fixes for v3.0.0
2 parents 8bd55a4 + f11f3ce commit af305f4

9 files changed

Lines changed: 129 additions & 60 deletions

File tree

spock/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
"config",
2424
"directory",
2525
"file",
26+
"helpers",
2627
"SavePath",
2728
"spock",
2829
"SpockBuilder",
30+
"utils",
2931
]
3032

3133
__version__ = get_versions()["version"]

spock/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ __all__ = [
2323
"config",
2424
"directory",
2525
"file",
26+
"helpers",
2627
"SavePath",
2728
"spock",
2829
"SpockBuilder",
30+
"utils",
2931
]
3032

3133
_T = TypeVar("_T")

spock/backend/builder.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
import argparse
88
from abc import ABC, abstractmethod
99
from enum import EnumMeta
10-
from typing import ByteString, Dict, List, Optional, Tuple
10+
from typing import ByteString, Dict, List, Optional, Set, Tuple
1111

1212
import attr
1313

1414
from spock.args import SpockArguments
1515
from spock.backend.field_handlers import RegisterSpockCls
1616
from spock.backend.help import attrs_help
17+
from spock.backend.resolvers import VarResolver
1718
from spock.backend.spaces import BuilderSpace
1819
from spock.backend.wrappers import Spockspace
1920
from spock.exceptions import _SpockInstantiationError
@@ -196,17 +197,23 @@ def resolve_spock_space_kwargs(self, graph: Graph, dict_args: Dict) -> Dict:
196197
# dependencies in the correct order
197198
for spock_name in merged_graph.topological_order:
198199
# First we check for any needed cls dependent variable resolution
199-
cls_fields = var_graph.resolve(spock_name, builder_space.spock_space)
200+
cls_fields, cls_changed_vars = var_graph.resolve(
201+
spock_name, builder_space.spock_space
202+
)
200203
# Then we map cls references to their instantiated version
201204
cls_fields = self._clean_up_cls_refs(cls_fields, builder_space.spock_space)
202205
# Lastly we have to check for self-resolution -- we do this w/ yet another
203206
# graph -- graphs FTW! -- this maps back to the fields dict in the tuple
204-
cls_fields = SelfGraph(
207+
cls_fields, var_changed_vars = SelfGraph(
205208
cls_fields_dict[spock_name]["cls"], cls_fields
206209
).resolve()
207-
208-
# Once all resolution occurs we attempt to instantiate the cls
210+
# Get the actual underlying class
209211
spock_cls = merged_graph.node_map[spock_name]
212+
# Merge the changed sets -- then attempt to cast them all post resolution
213+
self._cast_all_maps(
214+
spock_cls, cls_fields, cls_changed_vars | var_changed_vars
215+
)
216+
# Once all resolution occurs we attempt to instantiate the cls
210217
try:
211218
spock_instance = spock_cls(**cls_fields)
212219
except Exception as e:
@@ -218,6 +225,25 @@ def resolve_spock_space_kwargs(self, graph: Graph, dict_args: Dict) -> Dict:
218225
builder_space.spock_space[spock_cls.__name__] = spock_instance
219226
return builder_space.spock_space
220227

228+
@staticmethod
229+
def _cast_all_maps(cls, cls_fields: Dict, changed_vars: Set) -> None:
230+
"""Casts all the resolved references to the requested type
231+
232+
Args:
233+
cls: current spock class
234+
cls_fields: current fields dictionary to attempt cast within
235+
changed_vars: set of resolved variables that need to be cast
236+
237+
Returns:
238+
239+
"""
240+
for val in changed_vars:
241+
cls_fields[val] = VarResolver._attempt_cast(
242+
maybe_env=cls_fields[val],
243+
value_type=getattr(cls.__attrs_attrs__, val).type,
244+
ref_value=val,
245+
)
246+
221247
@staticmethod
222248
def _clean_up_cls_refs(fields: Dict, spock_space: Dict) -> Dict:
223249
"""Swaps in the newly created cls if it hasn't been instantiated yet

spock/backend/validators.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def _is_file(type: _T, check_access: bool, attr: attr.Attribute, value: str) ->
6060
_check_instance(value, attr.name, str)
6161
# # If so then cast to underlying type
6262
# value = file(value)
63-
if not Path(value).is_file():
63+
if not Path(value).resolve().is_file():
6464
raise ValueError(f"{attr.name} must be a file: {value} is not a valid file")
6565
r = os.access(value, os.R_OK)
6666
w = os.access(value, os.W_OK)
@@ -141,13 +141,13 @@ def _is_directory(
141141
# Check the instance type first
142142
_check_instance(value, attr.name, str)
143143
# If it's not a path and not flagged to create then raise exception
144-
if not Path(value).is_dir() and not create:
144+
if not Path(value).resolve().is_dir() and not create:
145145
raise ValueError(
146146
f"{attr.name} must be a directory: {value} is not a " f"valid directory"
147147
)
148148
# Else just try and create the path -- exist_ok means if the path already exists
149149
# it won't throw an exception
150-
elif not Path(value).is_dir() and create:
150+
elif not Path(value).resolve().is_dir() and create:
151151
try:
152152
os.makedirs(value, exist_ok=True)
153153
print(

spock/builder.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -640,21 +640,26 @@ def obj_2_dict(self, obj: Union[_C, List[_C], Tuple[_C, ...]]) -> Dict[str, Dict
640640
Returns:
641641
dictionary where the class names are keys and the values are the dictionary representations
642642
"""
643-
if isinstance(obj, (List, Tuple)):
644-
obj_dict = {}
645-
for val in obj:
646-
if not _is_spock_instance(val):
647-
raise _SpockValueError(
648-
f"Object is not a @spock decorated class object -- currently `{type(val)}`"
649-
)
650-
obj_dict.update({type(val).__name__: val})
651-
elif _is_spock_instance(obj):
652-
obj_dict = {type(obj).__name__: obj}
653-
else:
654-
raise _SpockValueError(
655-
f"Object is not a @spock decorated class object -- currently `{type(obj)}`"
656-
)
657-
return self.spockspace_2_dict(Spockspace(**obj_dict))
643+
644+
from spock.helpers import to_dict
645+
646+
return to_dict(obj, self._saver_obj)
647+
648+
# if isinstance(obj, (List, Tuple)):
649+
# obj_dict = {}
650+
# for val in obj:
651+
# if not _is_spock_instance(val):
652+
# raise _SpockValueError(
653+
# f"Object is not a @spock decorated class object -- currently `{type(val)}`"
654+
# )
655+
# obj_dict.update({type(val).__name__: val})
656+
# elif _is_spock_instance(obj):
657+
# obj_dict = {type(obj).__name__: obj}
658+
# else:
659+
# raise _SpockValueError(
660+
# f"Object is not a @spock decorated class object -- currently `{type(obj)}`"
661+
# )
662+
# return self.spockspace_2_dict(Spockspace(**obj_dict))
658663

659664
def evolve(self, *args: _C) -> Spockspace:
660665
"""Function that allows a user to evolve the underlying spock classes with

spock/graph.py

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,16 @@ def reverse_map(self):
257257
def nodes(self):
258258
return [k.name for k in self._cls.__attrs_attrs__]
259259

260-
def _cast_all_maps(self, changed_vars: Set):
261-
for val in changed_vars:
262-
self._fields[val] = self.var_resolver.attempt_cast(
263-
self._fields[val], getattr(self._cls.__attrs_attrs__, val).type, val
264-
)
260+
def resolve(self) -> Tuple[Dict, Set]:
261+
"""Resolves variable references by searching thorough the current spock_space
262+
263+
Args:
265264
266-
def resolve(self) -> Dict:
265+
Returns:
266+
field dictionary containing the resolved values and a set containing all
267+
changed variables to delay casting post resolution
268+
269+
"""
267270
# Iterate in topo order
268271
for k in self.topological_order:
269272
# get the self dependent values and swap within the fields dict
@@ -275,9 +278,8 @@ def resolve(self) -> Dict:
275278
name=v,
276279
)
277280
self._fields[v] = typed_val
278-
# Get a set of all changed variables and attempt to cast them
279-
self._cast_all_maps(set(self._ref_map.keys()))
280-
return self._fields
281+
# Get a set of all changed variables
282+
return self._fields, set(self._ref_map.keys())
281283

282284
def _build(self) -> Tuple[Dict, Dict]:
283285
"""Builds a dictionary of nodes and their edges (essentially builds the DAG)
@@ -363,35 +365,20 @@ def ref_2_resolve(self) -> Set:
363365
"""Returns the values that need to be resolved"""
364366
return set(self.ref_map.keys())
365367

366-
def _cast_all_maps(self, cls_name: str, changed_vars: Set) -> None:
367-
"""Casts all the resolved references to the requested type
368-
369-
Args:
370-
cls_name: name of the underlying class
371-
changed_vars: set of resolved variables that need to be cast
372-
373-
Returns:
374-
375-
"""
376-
for val in changed_vars:
377-
self.cls_map[cls_name][val] = self.var_resolver.attempt_cast(
378-
self.cls_map[cls_name][val],
379-
getattr(self.node_map[cls_name].__attrs_attrs__, val).type,
380-
val,
381-
)
382-
383-
def resolve(self, spock_cls: str, spock_space: Dict) -> Dict:
368+
def resolve(self, spock_cls: str, spock_space: Dict) -> Tuple[Dict, Set]:
384369
"""Resolves variable references by searching thorough the current spock_space
385370
386371
Args:
387372
spock_cls: name of the spock class
388373
spock_space: current spock_space to look for the underlying value
389374
390375
Returns:
391-
field dictionary containing the resolved values
376+
field dictionary containing the resolved values and a set containing all
377+
changed variables to delay casting post resolution
392378
393379
"""
394380
# First we check for any needed variable resolution
381+
changed_vars = set()
395382
if spock_cls in self.ref_2_resolve:
396383
# iterate over the mapped refs to swap values -- using the var resolver
397384
# to get the correct values
@@ -406,11 +393,10 @@ def resolve(self, spock_cls: str, spock_space: Dict) -> Dict:
406393
)
407394
# Swap the value to the replaced version
408395
self.cls_map[spock_cls][ref["val"]] = typed_val
409-
# Get a set of all changed variables and attempt to cast them
396+
# Get a set of all changed variables
410397
changed_vars = {n["val"] for n in self.ref_map[spock_cls]}
411-
self._cast_all_maps(spock_cls, changed_vars)
412398
# Return the field dict
413-
return self.cls_map[spock_cls]
399+
return self.cls_map[spock_cls], changed_vars
414400

415401
def _build(self) -> Tuple[Dict, Dict]:
416402
"""Builds a dictionary of nodes and their edges (essentially builds the DAG)

spock/helpers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""Helper functions for Spock"""
6+
7+
from typing import Dict, List, Optional, Tuple, Union
8+
9+
from spock.backend.saver import AttrSaver
10+
from spock.backend.wrappers import Spockspace
11+
from spock.exceptions import _SpockValueError
12+
from spock.utils import _C, _is_spock_instance
13+
14+
15+
def to_dict(
16+
objs: Union[_C, List[_C], Tuple[_C, ...]], saver: Optional[AttrSaver] = AttrSaver()
17+
) -> Dict[str, Dict]:
18+
"""Converts spock classes from a Spockspace into their dictionary representations
19+
20+
Args:
21+
objs: single spock class or an iterable of spock classes
22+
saver: optional saver class object
23+
24+
Returns:
25+
dictionary where the class names are keys and the values are the dictionary
26+
representations
27+
"""
28+
if isinstance(objs, (List, Tuple)):
29+
obj_dict = {}
30+
for val in objs:
31+
if not _is_spock_instance(val):
32+
raise _SpockValueError(
33+
f"Object is not a @spock decorated class object -- "
34+
f"currently `{type(val)}`"
35+
)
36+
obj_dict.update({type(val).__name__: val})
37+
elif _is_spock_instance(objs):
38+
obj_dict = {type(objs).__name__: objs}
39+
else:
40+
raise _SpockValueError(
41+
f"Object is not a @spock decorated class object -- "
42+
f"currently `{type(objs)}`"
43+
)
44+
return saver.dict_payload(Spockspace(**obj_dict))

spock/utils.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,6 @@ def _get_callable_type():
9292
return _VariadicGenericAlias
9393

9494

95-
def _get_new_type():
96-
97-
pass
98-
99-
10095
_SpockGenericAlias = _get_alias_type()
10196
_SpockVariadicGenericAlias = _get_callable_type()
10297
_T = TypeVar("_T")

tests/base/test_resolvers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ class FooBar:
7474
val: int = 12
7575

7676

77+
@spock
78+
class OtherRef:
79+
other_str: str = "yes"
80+
81+
7782
@spock
7883
class RefClass:
7984
a_float: float = 12.1
@@ -113,6 +118,9 @@ class RefClassDefault:
113118
ref_nested_to_str: str = "${spock.var:FooBar.val}.${spock.var:Lastly.tester}"
114119
ref_nested_to_float: float = "${spock.var:FooBar.val}.${spock.var:Lastly.tester}"
115120
ref_self: float = "${spock.var:RefClassDefault.ref_float}"
121+
ref_self_nested: str = (
122+
"${spock.var:RefClassDefault.ref_string}-${spock.var:OtherRef.other_str}"
123+
)
116124

117125

118126
class TestRefResolver:
@@ -162,7 +170,7 @@ def test_from_def(self, monkeypatch):
162170
with monkeypatch.context() as m:
163171
m.setattr(sys, "argv", [""])
164172
config = SpockBuilder(
165-
RefClassDefault, RefClass, Lastly, BarFoo, FooBar
173+
RefClassDefault, RefClass, Lastly, BarFoo, FooBar, OtherRef
166174
).generate()
167175

168176
assert config.RefClassDefault.ref_float == 12.1
@@ -172,6 +180,7 @@ def test_from_def(self, monkeypatch):
172180
assert config.RefClassDefault.ref_nested_to_str == "12.1"
173181
assert config.RefClassDefault.ref_nested_to_float == 12.1
174182
assert config.RefClassDefault.ref_self == config.RefClassDefault.ref_float
183+
assert config.RefClassDefault.ref_self_nested == "helloo-yes"
175184

176185

177186
@spock

0 commit comments

Comments
 (0)