Skip to content

Commit 5590b7c

Browse files
authored
Merge pull request #147 from python-odin/development
Relese 2.5
2 parents b44e09f + 5f14f36 commit 5590b7c

13 files changed

Lines changed: 368 additions & 209 deletions

File tree

HISTORY

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
2.5
2+
===
3+
4+
- Add ValidationErrorCollection helper for simplifying collection of errors in custom validation code.
5+
- Change behaviour of to_python method on a CompositeField to not do a full clean.
6+
This is not required as this should be completed during the validation stage.
7+
This prevents double validation and solves the issue of resources not being populated at all if
8+
any contained field contains an error.
9+
110
2.4
211
===
312

docs/ref/helpers.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.. automodule:: odin.helpers
2+
:members:

docs/ref/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ API Reference
1414
validators
1515
traversal
1616
decorators
17+
helpers
1718
utils

poetry.lock

Lines changed: 153 additions & 166 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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.4"
7+
version = "2.5"
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/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from odin.proxy import ResourceProxy # noqa
1414
from odin.annotated_resource import * # noqa
1515
from odin.annotated_resource import type_aliases as types # noqa
16+
from odin.helpers import * # noqa
1617

1718
__authors__ = "Tim Savage <tim@savage.company>"
1819
__copyright__ = "Copyright (C) 2021 Tim Savage"

src/odin/contrib/rich/theme.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Rich Theme definition."""
2+
from typing import Dict
3+
4+
from rich import get_console
5+
from rich.theme import Theme
6+
from rich.style import Style
7+
8+
ODIN_STYLES: Dict[str, Style] = {
9+
"odin.resource.name": Style(color="bright_cyan"),
10+
"odin.resource.error": Style(color="red", underline=True),
11+
"odin.field.name": Style(color="bright_blue"),
12+
"odin.field.error": Style(color="red", italic=True),
13+
"odin.field.type": Style(color="magenta"),
14+
"odin.field.doc": Style(),
15+
}
16+
17+
odin_theme = Theme(ODIN_STYLES, inherit=False)
18+
19+
20+
def add_odin_theme():
21+
"""Add odin to builtin theme."""
22+
get_console().push_theme(odin_theme)

src/odin/contrib/rich/validation_tree.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Integration with Rich for nicer CLI's!"""
2+
from rich.text import Text
23
from typing import Iterable, Union
34

45
from rich.tree import Tree
56

67
from odin.exceptions import ValidationError, NON_FIELD_ERRORS
8+
from .theme import odin_theme
79

810

911
def _all_str(iterable: Iterable) -> bool:
@@ -15,18 +17,20 @@ def _validation_error_to_tree(error_messages: Union[list, dict], tree: Tree):
1517
"""Internal recursive method."""
1618

1719
if isinstance(error_messages, dict):
18-
for key, value in error_messages.items():
20+
for name, value in error_messages.items():
1921

2022
node = tree.add(
21-
f"[yellow]:memo:" if key == NON_FIELD_ERRORS else f"[green]{key}"
23+
f"[odin.resource.name]+"
24+
if name == NON_FIELD_ERRORS
25+
else f"[odin.field.name]{name}"
2226
)
2327

2428
_validation_error_to_tree(value, node)
2529

2630
elif isinstance(error_messages, list):
2731
if _all_str(error_messages):
2832
for message in error_messages:
29-
tree.add(f"[italic]{message}", guide_style="bold")
33+
tree.add(f"[odin.field.error]{message}", guide_style="bold")
3034

3135
else:
3236
for idx, value in enumerate(error_messages):
@@ -47,6 +51,8 @@ def validation_error_tree(error: ValidationError, *, tree: Tree = None) -> Tree:
4751
print(tree)
4852
4953
"""
50-
tree = tree or Tree("[red bold]Validation Errors")
54+
tree = tree or Tree(
55+
"[red bold]Validation Errors",
56+
)
5157
_validation_error_to_tree(error.error_messages, tree)
5258
return tree

src/odin/contrib/sphinx/__init__.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -153,44 +153,46 @@ def document_members(self, all_members: bool = False) -> None:
153153
for f in field_iter(self.object, self.options.include_virtual)
154154
]
155155

156-
# Calculate table column widths
157-
name_len = 4
158-
data_type_len = 9
159-
details_len = 7
160-
for name, data_type, details in data_table:
161-
name_len = max(len(name), name_len)
162-
data_type_len = max(len(data_type), data_type_len)
163-
details_len = max(max(len(l) for l in details), details_len)
164-
name_len += 2 # Padding
165-
data_type_len += 2 # Padding
166-
details_len += 2 # Padding
167-
168-
def add_separator(char="-"):
169-
self.add_line(
170-
f"+{char * name_len}+{char * data_type_len}+{char * details_len}+",
171-
"<odin_sphinx>",
172-
)
173-
174-
def add_row_line(name, data_type, details):
175-
self.add_line(
176-
f"| {name}{' ' * (name_len - len(name) - 2)} "
177-
f"| {data_type}{' ' * (data_type_len - len(data_type) - 2)} "
178-
f"| {details}{' ' * (details_len - len(details) - 2)} |",
179-
"<odin_sphinx>",
180-
)
181-
182-
def add_row(name, data_type, details):
183-
add_row_line(name, data_type, details.pop(0))
184-
for line in details:
185-
add_row_line("", "", line)
186-
187-
# Generate table
188-
add_separator()
189-
add_row("Name", "Data type", ["Details"])
190-
add_separator("=")
191-
for row in data_table:
192-
add_row(*row)
156+
# Generate output if there is any.
157+
if data_table:
158+
# Calculate table column widths
159+
name_len = 4
160+
data_type_len = 9
161+
details_len = 7
162+
for name, data_type, details in data_table:
163+
name_len = max(len(name), name_len)
164+
data_type_len = max(len(data_type), data_type_len)
165+
details_len = max(max(len(l) for l in details), details_len)
166+
name_len += 2 # Padding
167+
data_type_len += 2 # Padding
168+
details_len += 2 # Padding
169+
170+
def add_separator(char="-"):
171+
self.add_line(
172+
f"+{char * name_len}+{char * data_type_len}+{char * details_len}+",
173+
"<odin_sphinx>",
174+
)
175+
176+
def add_row_line(name, data_type, details):
177+
self.add_line(
178+
f"| {name}{' ' * (name_len - len(name) - 2)} "
179+
f"| {data_type}{' ' * (data_type_len - len(data_type) - 2)} "
180+
f"| {details}{' ' * (details_len - len(details) - 2)} |",
181+
"<odin_sphinx>",
182+
)
183+
184+
def add_row(name, data_type, details):
185+
add_row_line(name, data_type, details.pop(0))
186+
for line in details:
187+
add_row_line("", "", line)
188+
189+
# Generate table
193190
add_separator()
191+
add_row("Name", "Data type", ["Details"])
192+
add_separator("=")
193+
for row in data_table:
194+
add_row(*row)
195+
add_separator()
194196

195197

196198
def setup(app: Sphinx):

src/odin/fields/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,10 @@ def __init__(self, **options):
649649
options.setdefault("default", dict)
650650
super().__init__(**options)
651651

652+
def __iter__(self):
653+
# This does nothing but it does prevent inspections from complaining.
654+
return None # NoQA
655+
652656
def to_python(self, value):
653657
if value is None:
654658
return value
@@ -674,6 +678,10 @@ def __init__(self, **options):
674678
options.setdefault("default", list)
675679
super().__init__(**options)
676680

681+
def __iter__(self):
682+
# This does nothing but it does prevent inspections from complaining.
683+
return None # NoQA
684+
677685
def to_python(self, value):
678686
if value is None:
679687
return value

0 commit comments

Comments
 (0)