Skip to content

Commit 004b425

Browse files
authored
Merge pull request #141 from python-odin/development
Release 2.3.1
2 parents 8792e6a + ff11c6b commit 004b425

9 files changed

Lines changed: 96 additions & 35 deletions

File tree

HISTORY

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2.3.1
2+
=====
3+
4+
Bug fix
5+
-------
6+
7+
- ResourceOptions.composite_fields filtered composite field by Resource instead of ResourceBase.
8+
19
2.3
210
===
311

docs/intro/loading-and-saving-data.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,20 @@ Using the Book and Author resources presented in the :doc:`creating-resources` s
3232
deserialization of data.
3333

3434
Similarly data can be deserialized back into an object graph using the :py:meth:`odin.codecs.json_codec.loads` method.
35+
36+
37+
Other file formats
38+
==================
39+
40+
Odin includes codecs for many different file formats including:
41+
42+
- :doc:`../ref/codecs/yaml_codec`
43+
- :doc:`../ref/codecs/toml_codec`
44+
- :doc:`../ref/codecs/msgpack_codec`
45+
- :doc:`../ref/codecs/xml_codec` [#f1]_
46+
47+
Or using each resource as a row:
48+
49+
- :doc:`../ref/codecs/csv_codec`
50+
51+
.. [#f1] XML is write only

docs/ref/traversal.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,20 @@ Traversal package provides tools for iterating and navigating a resource tree.
1010
TraversalPath
1111
=============
1212

13-
*Todo*: In progress...
13+
A method of defining a location within a data structure, which can then be applied to
14+
the datastructure to extract the value.
15+
16+
A ``TraversalPath`` can be expressed as a string using ``.`` as a separator::
17+
18+
field1.field2
19+
20+
Both lists and dicts can be included using ``[]`` and ``{}`` syntax::
21+
22+
field[1].field2
23+
24+
or::
25+
26+
field{key=value}.field2
1427

1528

1629
ResourceTraversalIterator
@@ -23,3 +36,6 @@ This class has hooks that can be used by subclasses to customise the behaviour o
2336

2437
- *on_enter* - Called after entering a new resource.
2538
- *on_exit* - Called after exiting a resource.
39+
40+
.. autoclass:: odin.traversal.ResourceTraversalIterator
41+
:members:

docs/ref/utils.rst

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,45 @@ Collection of utilities for working with Odin as well as generic data manipulati
77
Resources
88
=========
99

10-
.. autofunc:: odin.utils.getmeta
10+
.. autofunction:: odin.utils.getmeta
1111

12-
.. autofunc:: odin.utils.field_iter
12+
.. autofunction:: odin.utils.field_iter
1313

14-
.. autofunc:: odin.utils.field_iter_items
14+
.. autofunction:: odin.utils.field_iter_items
1515

16-
.. autofunc:: odin.utils.virtual_field_iter_items
16+
.. autofunction:: odin.utils.virtual_field_iter_items
1717

18-
.. autofunc:: odin.utils.attribute_field_iter_items
18+
.. autofunction:: odin.utils.attribute_field_iter_items
1919

20-
.. autofunc:: odin.utils.element_field_iter_items
20+
.. autofunction:: odin.utils.element_field_iter_items
2121

22-
.. autofunc:: odin.utils.extract_fields_from_dict
22+
.. autofunction:: odin.utils.extract_fields_from_dict
2323

2424

2525
Name Manipulation
2626
=================
2727

28-
.. autofunc:: odin.utils.camel_to_lower_separated
28+
.. autofunction:: odin.utils.camel_to_lower_separated
2929

30-
.. autofunc:: odin.utils.camel_to_lower_underscore
30+
.. autofunction:: odin.utils.camel_to_lower_underscore
3131

32-
.. autofunc:: odin.utils.camel_to_lower_dash
32+
.. autofunction:: odin.utils.camel_to_lower_dash
3333

34-
.. autofunc:: odin.utils.lower_underscore_to_camel
34+
.. autofunction:: odin.utils.lower_underscore_to_camel
3535

36-
.. autofunc:: odin.utils.lower_dash_to_camel
36+
.. autofunction:: odin.utils.lower_dash_to_camel
3737

3838

3939
Choice Generation
4040
=================
4141

42-
.. autofunc:: odin.utils.value_in_choices
42+
.. autofunction:: odin.utils.value_in_choices
4343

44-
.. autofunc:: odin.utils.iter_to_choices
44+
.. autofunction:: odin.utils.iter_to_choices
4545

4646

4747

4848
Iterables
4949
=========
5050

51-
.. autofunc:: odin.utils.chunk
51+
.. autofunction:: odin.utils.chunk

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "odin"
7-
version = "2.3"
7+
version = "2.3.1"
88
description = "Data-structure definition/validation/traversal, mapping and serialisation toolkit for Python"
99
authors = ["Tim Savage <tim@savage.company>"]
1010
license = "BSD-3-Clause"

src/odin/filtering.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88

99

1010
class FilterAtom(abc.ABC):
11-
"""
12-
Base filter statement
13-
"""
11+
"""Base filter statement"""
1412

1513
__slots__ = ()
1614

src/odin/resources.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ def composite_fields(self) -> Sequence[Field]:
171171
"""All composite fields."""
172172
# Not the nicest solution but is a fairly safe way of detecting a composite field.
173173
return tuple(
174-
f for f in self.fields if (hasattr(f, "of") and issubclass(f.of, Resource))
174+
f
175+
for f in self.fields
176+
if (hasattr(f, "of") and issubclass(f.of, ResourceBase))
175177
)
176178

177179
@cached_property

src/odin/traversal.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Union
1+
"""Traversal of a datastructure."""
2+
from typing import Union, Sequence, Iterable, Optional, Tuple, Type
23

34
from odin.utils import getmeta
45

@@ -10,7 +11,13 @@ class NotSupplied:
1011
pass
1112

1213

13-
def _split_atom(atom):
14+
NotSuppliedType = Type[NotSupplied]
15+
OptionalStr = Union[str, NotSuppliedType]
16+
PathAtom = Tuple[OptionalStr, OptionalStr, str]
17+
18+
19+
def _split_atom(atom: str) -> PathAtom:
20+
"""Split a section of a path into lookups that can be used to navigate a path."""
1421
if "[" in atom:
1522
field, _, idx = atom.rstrip("]").partition("[")
1623
return idx, NotSupplied, field
@@ -26,19 +33,23 @@ class TraversalPath:
2633
"""A path through a resource structure."""
2734

2835
@classmethod
29-
def parse(cls, path: Union["TraversalPath", str]):
36+
def parse(cls, path: Union["TraversalPath", str]) -> Optional["TraversalPath"]:
37+
"""Parse a traversal path string."""
3038
if isinstance(path, TraversalPath):
3139
return path
3240
if isinstance(path, str):
3341
return cls(*[_split_atom(a) for a in path.split(".")])
3442

35-
def __init__(self, *path):
43+
__slots__ = ("_path",)
44+
45+
def __init__(self, *path: PathAtom):
46+
"""Initialise traversal path"""
3647
self._path = path
3748

3849
def __repr__(self):
3950
return f"<TraversalPath: {self}>"
4051

41-
def __str__(self):
52+
def __str__(self) -> str:
4253
atoms = []
4354
for value, key, field in self._path:
4455
if value is NotSupplied:
@@ -49,15 +60,18 @@ def __str__(self):
4960
atoms.append(f"{field}{{{key}={value}}}")
5061
return ".".join(atoms)
5162

52-
def __hash__(self):
63+
def __hash__(self) -> int:
64+
"""Hash of the path."""
5365
return hash(self._path)
5466

55-
def __eq__(self, other):
67+
def __eq__(self, other) -> bool:
68+
"""Compare to another path."""
5669
if isinstance(other, TraversalPath):
5770
return hash(self) == hash(other)
5871
return NotImplemented
5972

60-
def __add__(self, other):
73+
def __add__(self, other) -> "TraversalPath":
74+
"""Join paths together."""
6175
if isinstance(other, TraversalPath):
6276
return TraversalPath(*(self._path + other._path))
6377

@@ -69,7 +83,8 @@ def __add__(self, other):
6983

7084
raise TypeError(f"Cannot add '{other}' to a path.")
7185

72-
def __iter__(self):
86+
def __iter__(self) -> Iterable[PathAtom]:
87+
"""Iterate a path returning each element on the path."""
7388
return iter(self._path)
7489

7590
def get_value(self, root_resource: ResourceBase):
@@ -126,7 +141,10 @@ class ResourceTraversalIterator:
126141
127142
"""
128143

129-
def __init__(self, resource):
144+
__slots__ = ("_resource_iters", "_field_iters", "_path", "_resource_stack")
145+
146+
def __init__(self, resource: Union[ResourceBase, Sequence[ResourceBase]]):
147+
"""Initialise instance with the initial resource or sequence of resources."""
130148
if isinstance(resource, (list, tuple)):
131149
# Stack of resource iterators (starts initially with entries from the list)
132150
self._resource_iters = [iter([(i, r) for i, r in enumerate(resource)])]
@@ -139,10 +157,12 @@ def __init__(self, resource):
139157
self._path = [(NotSupplied, NotSupplied, NotSupplied)]
140158
self._resource_stack = [None]
141159

142-
def __iter__(self):
160+
def __iter__(self) -> Iterable[ResourceBase]:
161+
"""Obtain an iterable instance."""
143162
return self
144163

145-
def __next__(self):
164+
def __next__(self) -> ResourceBase:
165+
"""Get next resource instance."""
146166
if self._resource_iters:
147167
if self._field_iters:
148168
# Check if the last entry in the field stack has any unprocessed fields.
@@ -211,7 +231,7 @@ def depth(self) -> int:
211231
return len(self._path) - 1
212232

213233
@property
214-
def current_resource(self):
234+
def current_resource(self) -> Optional[ResourceBase]:
215235
"""The current resource being traversed."""
216236
if self._resource_stack:
217237
return self._resource_stack[-1]

tests/test_traversal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Meta:
6767

6868
class ResourceTraversalIteratorTest(traversal.ResourceTraversalIterator):
6969
def __init__(self, resource):
70-
super(ResourceTraversalIteratorTest, self).__init__(resource)
70+
super().__init__(resource)
7171
self.events = []
7272

7373
def on_pre_enter(self):

0 commit comments

Comments
 (0)