Skip to content

Commit dede438

Browse files
authored
Merge pull request #134 from python-odin/development
Release 2.1
2 parents 5eb8dab + 541775b commit dede438

13 files changed

Lines changed: 476 additions & 383 deletions

File tree

HISTORY

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
2.1
2+
===
3+
4+
Annotated Resources
5+
-------------------
6+
7+
- Final fields are now mapped to ConstantField and require a default value.
8+
- Fix issue where the Toml codec did not recognise annotated resources in dump and dumps functions.
9+
10+
Breaking changes
11+
----------------
12+
13+
- Virtual fields ConstantField and CalculatedField now require keyword arguments for options.
14+
115
2.0
216
===
317

poetry.lock

Lines changed: 268 additions & 190 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Type aliases for string formatted types."""
12
from typing import Callable, Union, Any, Sequence, Tuple
23

34
__all__ = (
@@ -19,40 +20,30 @@
1920

2021

2122
class Email(str):
22-
"""
23-
Alias for use defining email entries
24-
"""
23+
"""Alias for use defining email entries."""
2524

2625
__slots__ = ()
2726

2827

2928
class IPv4(str):
30-
"""
31-
Alias for use defining IPv4 entries
32-
"""
29+
"""Alias for use defining IPv4 entries."""
3330

3431
__slots__ = ()
3532

3633

3734
class IPv6(str):
38-
"""
39-
Alias for use defining IPv6 entries
40-
"""
35+
"""Alias for use defining IPv6 entries."""
4136

4237
__slots__ = ()
4338

4439

4540
class IPv46(str):
46-
"""
47-
Alias for use defining IPv4 or IPv6 entries
48-
"""
41+
"""Alias for use defining IPv4 or IPv6 entries."""
4942

5043
__slots__ = ()
5144

5245

5346
class Url(str):
54-
"""
55-
Alias for use defining IPv4 or IPv6 entries
56-
"""
47+
"""Alias for use defining URL entries."""
5748

5849
__slots__ = ()

src/odin/annotated_resource/type_resolution.py

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
"""Methods for resolving types to fields."""
12
import datetime
23
import enum
34
import pathlib
45
import re
56
import uuid
6-
from typing import Any, Sequence, Dict, Type, Union, get_origin, List
7+
from typing import Any, Sequence, Dict, Type, Union, get_origin, List, Final
78

89
try:
910
# Handle the change in typing between 3.8 and later releases
@@ -27,13 +28,12 @@
2728
ListField,
2829
DictField,
2930
)
31+
from ..fields.virtual import ConstantField
3032
from ..resources import ResourceBase
3133

3234

3335
class Options:
34-
"""
35-
Define options for a field
36-
"""
36+
"""Define options for a field"""
3737

3838
__slots__ = (
3939
"field_type",
@@ -78,18 +78,14 @@ def __init__(
7878
}
7979

8080
def _kwargs(self):
81-
"""
82-
Build kwargs used to instantiate field
83-
"""
81+
"""Build kwargs used to instantiate field"""
8482
final_args = self.base_args
8583
if issubclass(self.field_type, Field):
8684
final_args.update(self.field_args)
8785
return final_args
8886

8987
def _improve_error(self, ex: TypeError):
90-
"""
91-
Attempt to provide more context for the error
92-
"""
88+
"""Attempt to provide more context for the error"""
9389
message = str(ex)
9490

9591
for check in (
@@ -103,16 +99,16 @@ def _improve_error(self, ex: TypeError):
10399
)
104100

105101
def init_field(self):
106-
"""
107-
Instantiate field object
108-
"""
102+
"""Instantiate field object"""
109103
if self.field_type:
110104
try:
111105
return self.field_type(**self._kwargs())
112106
except TypeError as ex:
113107
self._improve_error(ex)
114108
raise
115-
raise ResourceDefError("Field type could not be resolved")
109+
raise ResourceDefError(
110+
f"Field type `{self.base_args.get('name', 'Unknown!')}` could not be resolved"
111+
)
116112

117113

118114
SIMPLE_TYPE_MAP = {
@@ -138,9 +134,7 @@ def init_field(self):
138134

139135

140136
def _resolve_field_from_type(options: Options, type_: type):
141-
"""
142-
Resolve a field from a basic type
143-
"""
137+
"""Resolve a field from a basic type"""
144138
if field_type := SIMPLE_TYPE_MAP.get(type_, None):
145139
options.field_type = field_type
146140

@@ -159,19 +153,15 @@ def _resolve_field_from_type(options: Options, type_: type):
159153

160154

161155
def is_optional(type_: Union) -> bool:
162-
"""
163-
Field is an optional type
164-
"""
156+
"""Field is an optional type"""
165157
args = type_.__args__
166158
return (
167159
len(args) == 2 and type(None) in args
168160
) # pylint: disable=unidiomatic-typecheck
169161

170162

171163
def _resolve_list_from_sub_scripted_type(args: Sequence[Any], options: Options):
172-
"""
173-
Handle the various types of sequence type
174-
"""
164+
"""Handle the various types of sequence type"""
175165
options.field_args["default"] = list
176166

177167
(field,) = args
@@ -189,9 +179,7 @@ def _resolve_list_from_sub_scripted_type(args: Sequence[Any], options: Options):
189179

190180

191181
def _resolve_dict_from_sub_scripted_type(args: Sequence[Any], options: Options):
192-
"""
193-
Handle the various types of sequence type
194-
"""
182+
"""Handle the various types of sequence type"""
195183
options.field_args["default"] = dict
196184

197185
key_field, value_field = args
@@ -214,9 +202,7 @@ def _resolve_dict_from_sub_scripted_type(args: Sequence[Any], options: Options):
214202

215203

216204
def _resolve_field_from_sub_scripted_type(origin: Type, options: Options, type_):
217-
"""
218-
Resolve a field from a generic type
219-
"""
205+
"""Resolve a field from a generic type"""
220206
args = getattr(type_, "__args__", None)
221207
if not args:
222208
# This occurs with the plain List and Dict types which are essentially
@@ -229,6 +215,17 @@ def _resolve_field_from_sub_scripted_type(origin: Type, options: Options, type_)
229215
if is_optional(type_):
230216
options.field_args["null"] = True
231217
return _resolve_field_from_annotation(options, args[0])
218+
else:
219+
pass # Resolve
220+
221+
elif origin is Final:
222+
# Constant
223+
options.field_type = ConstantField
224+
value = options.field_args["default"]
225+
if value is NotProvided:
226+
raise ResourceDefError(f"Final fields require a value")
227+
options.base_args["value"] = value
228+
return
232229

233230
elif issubclass(origin, List):
234231
return _resolve_list_from_sub_scripted_type(args, options)
@@ -267,9 +264,7 @@ def process_attribute(
267264
*,
268265
_base_field: Type[BaseField] = BaseField,
269266
) -> BaseField:
270-
"""
271-
Process an individual attribute and generate a field based off values passed
272-
"""
267+
"""Process an individual attribute and generate a field based off values passed"""
273268

274269
# Attribute is already a field object
275270
if isinstance(value, _base_field):

src/odin/codecs/msgpack_codec.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Codec to load/save Message Pack (msgpack) documents."""
12
import datetime
23
import uuid
34
from typing import TextIO
@@ -9,7 +10,7 @@
910
"odin.codecs.msgpack_codec requires the 'msgpack-python' package."
1011
) # noqa
1112

12-
from odin import bases, Resource
13+
from odin import bases
1314
from odin import serializers, resources, ResourceAdapter
1415
from odin.utils import getmeta
1516

@@ -24,11 +25,9 @@
2425

2526

2627
class OdinPacker(msgpack.Packer):
27-
"""
28-
Encoder for Odin resources.
29-
"""
28+
"""Encoder for Odin resources."""
3029

31-
def __init__(self, include_virtual_fields=True, *args, **kwargs):
30+
def __init__(self, include_virtual_fields: bool = True, *args, **kwargs):
3231
kwargs.setdefault("default", self.default)
3332
super().__init__(*args, **kwargs)
3433
self.include_virtual_fields = include_virtual_fields
@@ -45,9 +44,13 @@ def default(self, o):
4544
return TYPE_SERIALIZERS[o.__class__](o)
4645

4746

48-
def load(fp, resource=None, full_clean=True, default_to_not_supplied=False):
49-
"""
50-
Load a from a MessagePack encoded file.
47+
def load(
48+
fp: TextIO,
49+
resource: resources.ResourceBase = None,
50+
full_clean: bool = True,
51+
default_to_not_supplied: bool = False,
52+
):
53+
"""Load a from a MessagePack encoded file.
5154
5255
See :py:meth:`loads` for more details of the loading operation.
5356
@@ -62,9 +65,13 @@ def load(fp, resource=None, full_clean=True, default_to_not_supplied=False):
6265
)
6366

6467

65-
def loads(s, resource=None, full_clean=True, default_to_not_supplied=False):
66-
"""
67-
Load from a MessagePack encoded string/bytes.
68+
def loads(
69+
s: str,
70+
resource: resources.ResourceBase = None,
71+
full_clean: bool = True,
72+
default_to_not_supplied: bool = False,
73+
):
74+
"""Load from a MessagePack encoded string/bytes.
6875
6976
If a ``resource`` value is supplied it is used as the base resource for the supplied MessagePack data. I one is not
7077
supplied a resource type field ``$`` is used to obtain the type represented by the dictionary. A ``ValidationError``
@@ -83,14 +90,13 @@ def loads(s, resource=None, full_clean=True, default_to_not_supplied=False):
8390

8491

8592
def dump(
86-
resource, # type: Resource
87-
fp, # type: TextIO,
93+
resource: resources.ResourceBase,
94+
fp: TextIO,
8895
cls=OdinPacker,
89-
include_virtual_fields=True, # type: bool
96+
include_virtual_fields: bool = True,
9097
**kwargs
9198
):
92-
"""
93-
Dump to a MessagePack encoded file.
99+
"""Dump to a MessagePack encoded file.
94100
95101
:param include_virtual_fields:
96102
:param resource: The root resource to dump to a MessagePack encoded file.
@@ -101,13 +107,12 @@ def dump(
101107

102108

103109
def dumps(
104-
resource, # type: Resource
110+
resource: resources.ResourceBase,
105111
cls=OdinPacker,
106-
include_virtual_fields=True, # type: bool
112+
include_virtual_fields: bool = True,
107113
**kwargs
108114
):
109-
"""
110-
Dump to a MessagePack encoded string.
115+
"""Dump to a MessagePack encoded string.
111116
112117
:param include_virtual_fields:
113118
:param resource: The root resource to dump to a MessagePack encoded file.

0 commit comments

Comments
 (0)