-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #147 from python-odin/development
Relese 2.5
- Loading branch information
Showing
13 changed files
with
368 additions
and
209 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.. automodule:: odin.helpers | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,5 @@ API Reference | |
validators | ||
traversal | ||
decorators | ||
helpers | ||
utils |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" | |
|
||
[tool.poetry] | ||
name = "odin" | ||
version = "2.4" | ||
version = "2.5" | ||
description = "Data-structure definition/validation/traversal, mapping and serialisation toolkit for Python" | ||
authors = ["Tim Savage <[email protected]>"] | ||
license = "BSD-3-Clause" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
from odin.proxy import ResourceProxy # noqa | ||
from odin.annotated_resource import * # noqa | ||
from odin.annotated_resource import type_aliases as types # noqa | ||
from odin.helpers import * # noqa | ||
|
||
__authors__ = "Tim Savage <[email protected]>" | ||
__copyright__ = "Copyright (C) 2021 Tim Savage" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""Rich Theme definition.""" | ||
from typing import Dict | ||
|
||
from rich import get_console | ||
from rich.theme import Theme | ||
from rich.style import Style | ||
|
||
ODIN_STYLES: Dict[str, Style] = { | ||
"odin.resource.name": Style(color="bright_cyan"), | ||
"odin.resource.error": Style(color="red", underline=True), | ||
"odin.field.name": Style(color="bright_blue"), | ||
"odin.field.error": Style(color="red", italic=True), | ||
"odin.field.type": Style(color="magenta"), | ||
"odin.field.doc": Style(), | ||
} | ||
|
||
odin_theme = Theme(ODIN_STYLES, inherit=False) | ||
|
||
|
||
def add_odin_theme(): | ||
"""Add odin to builtin theme.""" | ||
get_console().push_theme(odin_theme) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
""" | ||
Helpers | ||
~~~~~~~ | ||
A collection of useful convenience methods. | ||
""" | ||
|
||
from typing import Union, List, Dict, DefaultDict | ||
|
||
from odin import BaseField | ||
from odin.exceptions import NON_FIELD_ERRORS, ValidationError | ||
|
||
__all__ = ("ValidationErrorCollection",) | ||
|
||
|
||
class ValidationErrorCollection: | ||
"""Helper collection for collecting validation error messages and generating or raising an exception. | ||
Usage: | ||
.. code-block:: python | ||
errors = ValidationErrorCollection() | ||
... # Perform validation | ||
errors.add_message("name", "Value is required") | ||
if errors: | ||
raise errors.validation_error() | ||
""" | ||
|
||
def __init__(self): | ||
"""Initialise collection.""" | ||
self.error_messages = DefaultDict[str, List[str]](list) | ||
|
||
def __bool__(self): | ||
return bool(self.messages) | ||
|
||
@property | ||
def messages(self) -> Dict[str, List[str]]: | ||
"""Filtered messages that strips out empty messages.""" | ||
return { | ||
field_name: messages | ||
for field_name, messages in self.error_messages.items() | ||
if messages | ||
} | ||
|
||
def add_message(self, field: Union[str, BaseField], *messages): | ||
"""Append validation error message(s).""" | ||
field_name = field if isinstance(field, str) else field.attname | ||
self.error_messages[field_name].extend(messages) | ||
|
||
def add_resource_message(self, *messages): | ||
"""Append resource level validation error message(s).""" | ||
self.error_messages[NON_FIELD_ERRORS].extend(messages) | ||
|
||
def raise_if_defined(self): | ||
"""Raise an exception if any are defined.""" | ||
if self: | ||
raise self.validation_error() | ||
|
||
def validation_error(self) -> ValidationError: | ||
"""Generate an exception based on the validation messages added.""" | ||
return ValidationError(self.messages) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import pytest | ||
|
||
from odin import helpers | ||
from odin.exceptions import ValidationError | ||
from odin.utils import getmeta | ||
from .resources import Author | ||
|
||
|
||
class TestValidationErrorCollection: | ||
def test_messages__with_valid_message(self): | ||
target = helpers.ValidationErrorCollection() | ||
|
||
target.add_message("name", "this is required") | ||
|
||
assert target.messages == {"name": ["this is required"]} | ||
|
||
def test_messages__with_empty_messages(self): | ||
target = helpers.ValidationErrorCollection() | ||
|
||
target.add_message("name") | ||
|
||
assert target.messages == {} | ||
|
||
def test_add_message__with_string_field_name(self): | ||
target = helpers.ValidationErrorCollection() | ||
|
||
target.add_message("name", "this is required") | ||
|
||
assert target.messages == {"name": ["this is required"]} | ||
|
||
def test_add_message__with_field_instance(self): | ||
field = getmeta(Author).field_map["name"] | ||
target = helpers.ValidationErrorCollection() | ||
|
||
target.add_message(field, "this is required") | ||
|
||
assert target.messages == {"name": ["this is required"]} | ||
|
||
def test_add_resource_message(self): | ||
target = helpers.ValidationErrorCollection() | ||
|
||
target.add_resource_message("this is required") | ||
|
||
assert target.messages == {"__all__": ["this is required"]} | ||
|
||
def test_raise_if_defined__with_no_errors(self): | ||
target = helpers.ValidationErrorCollection() | ||
|
||
# Nothing should happen | ||
target.raise_if_defined() | ||
|
||
def test_raise_if_defined__with_errors(self): | ||
target = helpers.ValidationErrorCollection() | ||
target.add_message("name", "this is required") | ||
|
||
with pytest.raises(ValidationError): | ||
target.raise_if_defined() |