Skip to content

Commit b4ab566

Browse files
committed
Add tests for attrdict
1 parent b597252 commit b4ab566

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
lines changed

src/projspec/artifact/base.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ def make(self, *args, **kwargs):
5656
self._make(*args, **kwargs)
5757

5858
def _make(self, *args, **kwargs):
59-
subprocess.check_call(self.cmd, cwd=self.proj.url, **self.kw)
59+
subprocess.check_call(self.cmd, cwd=self.proj.url, **kwargs)
6060

6161
def remake(self, reqs=False):
62-
"""Recreate artifact and any runtime it depends on"""
62+
"""Recreate the artifact and any runtime it depends on"""
6363
if reqs:
6464
self.clean_req()
6565
self.clean()
@@ -82,12 +82,14 @@ def _repr2(self):
8282

8383
@classmethod
8484
def __init_subclass__(cls, **kwargs):
85-
registry[camel_to_snake(cls.__name__)] = cls
85+
sn = cls.snake_name()
86+
if sn in registry:
87+
raise RuntimeError()
88+
registry[sn] = cls
8689

87-
def to_json(self):
88-
out = self.__dict__.copy()
89-
out["cls"] = camel_to_snake(self.__name__)
90-
return out
90+
@classmethod
91+
def snake_name(cls):
92+
return camel_to_snake(cls.__name__)
9193

9294

9395
def get_artifact_cls(name: str) -> type[BaseArtifact]:

src/projspec/content/base.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ def _repr2(self):
2121

2222
@classmethod
2323
def __init_subclass__(cls, **kwargs):
24-
registry[camel_to_snake(cls.__name__)] = cls
24+
sn = cls.snake_name()
25+
if sn in registry:
26+
raise RuntimeError()
27+
registry[sn] = cls
28+
29+
@classmethod
30+
def snake_name(cls):
31+
return camel_to_snake(cls.__name__)
2532

2633

2734
def get_content_cls(name: str) -> type[BaseContent]:

src/projspec/proj/base.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def __init__(
3434
fs, path = fsspec.url_to_fs(path, **(storage_options or {}))
3535
self.fs = fs
3636
self.url = path
37-
self.specs = {}
38-
self.children = {}
37+
self.specs = AttrDict()
38+
self.children = AttrDict()
3939
self.excludes = excludes or default_excludes
4040
self.resolve(walk=walk, types=types)
4141

@@ -172,6 +172,10 @@ def __contains__(self, item) -> bool:
172172
item in _ for _ in self.children.values()
173173
)
174174

175+
def to_dict(self) -> dict:
176+
dic = AttrDict(specs=self.specs, children=self.children)
177+
return dic.to_dict()
178+
175179

176180
class ProjectSpec:
177181
"""A project specification
@@ -248,3 +252,7 @@ def __repr__(self):
248252
if self.artifacts:
249253
base += f"\nArtifacts:\n{yaml.dump(self.artifacts.to_dict(), Dumper=IndentDumper).rstrip()}\n"
250254
return base
255+
256+
def to_dict(self) -> dict:
257+
dic = AttrDict(contents=self.contents, artifacts=self.artifacts)
258+
return dic.to_dict()

src/projspec/utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def __init__(self, *data, **kw):
2424
if isinstance(data[0], dict):
2525
super().__init__(data[0])
2626
elif isinstance(data[0], list):
27+
if len(types) > 1:
28+
raise TypeError("Multiple types ina list")
2729
super().__init__(
2830
{camel_to_snake(next(iter(types)).__name__): data[0]}
2931
)
@@ -53,8 +55,10 @@ def to_dict(obj):
5355
from projspec.content import BaseContent
5456

5557
if isinstance(obj, dict):
56-
# includes AttrDict
57-
return {k: to_dict(v) for k, v in obj.items()}
58+
return {
59+
k: v.to_dict() if hasattr(v, "to_dict") else to_dict(v)
60+
for k, v in obj.items()
61+
}
5862
if isinstance(obj, (bytes, str)):
5963
return obj
6064
if isinstance(obj, Iterable):
@@ -91,7 +95,7 @@ def _linked_local_path(path):
9195

9296

9397
class IsInstalled:
94-
"""Checks if we can call commands, as a function of current environment"""
98+
"""Checks if we can call commands, as a function of the current environment."""
9599

96100
cache = {}
97101

@@ -120,7 +124,8 @@ def exists(self, cmd: str, refresh=False):
120124
return self.cache[(self.env, cmd)]
121125

122126
def __contains__(self, item):
123-
# canonical use: `"python" in is_installed`
127+
# canonical use:
128+
# "python" in is_installed`
124129
# shutil.which?
125130
return self.exists(item)
126131

tests/test_utils.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
1-
from projspec.utils import is_installed
1+
import pytest
2+
3+
from projspec.content import BaseContent
4+
from projspec.content.metadata import DescriptiveMetadata
5+
from projspec.utils import AttrDict, is_installed
26

37

48
def test_is_installed():
59
assert "python" in is_installed
10+
11+
12+
def test_attrdict():
13+
d = AttrDict({"a": 1, "b": 2, "c": 3})
14+
assert d.a == 1
15+
assert dict(d) == d
16+
17+
d2 = AttrDict(a=1, b=2, c=3)
18+
assert d2 == d
19+
20+
21+
def test_attrdict_entity():
22+
d = AttrDict(
23+
BaseContent(proj=None, artifacts=set()),
24+
DescriptiveMetadata(proj=None, artifacts=set()),
25+
)
26+
assert set(d) == {"base_content", "descriptive_metadata"}
27+
28+
with pytest.raises(TypeError):
29+
AttrDict(
30+
[
31+
BaseContent(proj=None, artifacts=set()),
32+
DescriptiveMetadata(proj=None, artifacts=set()),
33+
]
34+
)

0 commit comments

Comments
 (0)