Repository: s-knibbs/dataclasses-jsonschema
Pull request #123 (2019-12-06 05:38:28)

discriminator added to schema properties
Fixes issue #122 


Patch:
diff --git a/dataclasses_jsonschema/__init__.py b/dataclasses_jsonschema/__init__.py
index 58fe500..b78525a 100644
--- a/dataclasses_jsonschema/__init__.py
+++ b/dataclasses_jsonschema/__init__.py
@@ -6,7 +6,7 @@ from _decimal import Decimal
 from ipaddress import IPv4Address, IPv6Address
 from typing import Optional, Type, Union, Any, Dict, Tuple, List, TypeVar, get_type_hints, Callable
 import re
-from datetime import datetime
+from datetime import datetime, date
 from dataclasses import fields, is_dataclass, Field, MISSING, dataclass, asdict
 from uuid import UUID
 from enum import Enum
@@ -16,7 +16,7 @@ from typing_extensions import Final, Literal
 
 
 from dataclasses_jsonschema.field_types import (  # noqa: F401
-    FieldEncoder, DateTimeFieldEncoder, UuidField, DecimalField,
+    FieldEncoder, DateFieldEncoder, DateTimeFieldEncoder, UuidField, DecimalField,
     IPv4AddressField, IPv6AddressField, DateTimeField
 )
 from dataclasses_jsonschema.type_defs import JsonDict, SchemaType, JsonSchemaMeta, _NULL_TYPE, NULL  # noqa: F401
@@ -51,6 +51,10 @@ class ValidationError(Exception):
     pass
 
 
+class UnknownEnumValueError(ValueError):
+    pass
+
+
 def is_enum(field_type: Any):
     return issubclass_safe(field_type, Enum)
 
@@ -184,6 +188,7 @@ class JsonSchemaMixin:
     convert to and from JSON encodable dicts with validation against the schema
     """
     _field_encoders: Dict[Type, FieldEncoder] = {
+        date: DateFieldEncoder(),
         datetime: DateTimeFieldEncoder(),
         UUID: UuidField(),
         Decimal: DecimalField(),
@@ -283,7 +288,11 @@ class JsonSchemaMixin:
             elif is_final(field_type):
                 def encoder(ft, val, o): return cls._encode_field(unwrap_final(ft), val, o)
             elif is_enum(field_type):
-                def encoder(_, v, __): return v.value
+                def encoder(_, v, __):
+                    try:
+                        return v.value
+                    except AttributeError as e:
+                        raise UnknownEnumValueError(f'Unknown enum value: {v}') from e
             elif field_type_name == 'Union':
                 # Attempt to encode the field with each union variant.
                 # TODO: Find a more reliable method than this since in the case 'Union[List[str], Dict[str, int]]' this
@@ -293,7 +302,7 @@ class JsonSchemaMixin:
                     try:
                         encoded = cls._encode_field(variant, value, omit_none)
                         break
-                    except (TypeError, AttributeError):
+                    except (TypeError, AttributeError, UnknownEnumValueError):
                         continue
                 if encoded is None:
                     raise TypeError("No variant of '{}' matched the type '{}'".format(field_type, type(value)))
@@ -370,7 +379,12 @@ class JsonSchemaMixin:
         """
         data = {}
         for f in self._get_fields():
-            value = self._encode_field(f.field.type, getattr(self, f.field.name), omit_none)
+            value = getattr(self, f.field.name)
+            try:
+                value = self._encode_field(f.field.type, value, omit_none)
+            except UnknownEnumValueError as e:
+                warnings.warn(str(e))
+
             if omit_none and value is None:
                 continue
             if value is NULL:
@@ -764,6 +778,7 @@ class JsonSchemaMixin:
                     schema_options.schema_type == SchemaType.OPENAPI_3 and \
                     not cls.__discriminator_inherited:
                 schema['discriminator'] = {'propertyName': cls.__discriminator_name}
+                properties[cls.__discriminator_name] = {"type": "string"}
                 required.append(cls.__discriminator_name)
 
             # Needed for Draft 04 backwards compatibility
diff --git a/dataclasses_jsonschema/field_types.py b/dataclasses_jsonschema/field_types.py
index ed0933c..c85b967 100644
--- a/dataclasses_jsonschema/field_types.py
+++ b/dataclasses_jsonschema/field_types.py
@@ -1,5 +1,5 @@
 import warnings
-from datetime import datetime
+from datetime import datetime, date
 from decimal import Decimal
 from ipaddress import IPv4Address, IPv6Address
 from typing import cast, TypeVar, Generic, Optional
@@ -27,6 +27,20 @@ class FieldEncoder(Generic[T, OutType]):
         raise NotImplementedError()
 
 
+class DateFieldEncoder(FieldEncoder[date, str]):
+    """Encodes dates to RFC3339 format"""
+
+    def to_wire(self, value: date) -> str:
+        return value.isoformat()
+
+    def to_python(self, value: str) -> date:
+        return value if isinstance(value, date) else parse(cast(str, value)).date()
+
+    @property
+    def json_schema(self) -> JsonDict:
+        return {"type": "string", "format": "date"}
+
+
 class DateTimeFieldEncoder(FieldEncoder[datetime, str]):
     """Encodes datetimes to RFC3339 format"""
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 6f31218..54569b1 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -703,7 +703,10 @@ def test_discriminators():
         "Pet": {
             "description": "A generic pet",
             "type": "object",
-            "properties": {"name": {"type": "string"}},
+            "properties": {
+                "PetType": {"type": "string"},
+                "name": {"type": "string"}
+            },
             "required": ["name", "PetType"],
             "discriminator": {"propertyName": "PetType"}
         }
@@ -904,6 +907,9 @@ def test_unrecognized_enum_value():
     p = Pet.from_dict({'name': 'snakey', 'type': 'python'}, validate_enums=False)
     assert p.type == "python"
 
+    with pytest.warns(UserWarning):
+        assert p.to_dict() == {'name': 'snakey', 'type': 'python'}
+
 
 def test_inheritance_and_additional_properties_disallowed():
     @dataclass