Introduce JSONSchemaProperty

This commit is contained in:
Jeremy Stretch 2025-03-26 15:10:30 -04:00
parent 93bd2ee5b8
commit a67ea1305e

View File

@ -0,0 +1,124 @@
from dataclasses import dataclass
from enum import Enum
from typing import Any
from django import forms
from django.contrib.postgres.forms import SimpleArrayField
from django.core.validators import RegexValidator
from utilities.string import title
from utilities.validators import MultipleOfValidator
__all__ = (
'JSONSchemaProperty',
'PropertyTypeEnum',
'StringFormatEnum',
)
class PropertyTypeEnum(Enum):
STRING = 'string'
INTEGER = 'integer'
NUMBER = 'number'
BOOLEAN = 'boolean'
ARRAY = 'array'
OBJECT = 'object'
class StringFormatEnum(Enum):
EMAIL = 'email'
URI = 'uri'
IRI = 'iri'
UUID = 'uuid'
DATE = 'date'
TIME = 'time'
DATETIME = 'datetime'
FORM_FIELDS = {
PropertyTypeEnum.STRING.value: forms.CharField,
PropertyTypeEnum.INTEGER.value: forms.IntegerField,
PropertyTypeEnum.NUMBER.value: forms.FloatField,
PropertyTypeEnum.BOOLEAN.value: forms.BooleanField,
PropertyTypeEnum.ARRAY.value: SimpleArrayField,
PropertyTypeEnum.OBJECT.value: forms.JSONField,
}
STRING_FORM_FIELDS = {
StringFormatEnum.EMAIL.value: forms.EmailField,
StringFormatEnum.URI.value: forms.URLField,
StringFormatEnum.IRI.value: forms.URLField,
StringFormatEnum.UUID.value: forms.UUIDField,
StringFormatEnum.DATE.value: forms.DateField,
StringFormatEnum.TIME.value: forms.TimeField,
StringFormatEnum.DATETIME.value: forms.DateTimeField,
}
@dataclass
class JSONSchemaProperty:
type: PropertyTypeEnum = PropertyTypeEnum.STRING.value
title: str | None = None
description: str | None = None
default: Any = None
enum: list | None = None
# Strings
minLength: int | None = None
maxLength: int | None = None
pattern: str | None = None # Regex
format: StringFormatEnum | None = None
# Numbers
minimum: int | float | None = None
maximum: int | float | None = None
multipleOf: int | float | None = None
def to_form_field(self, name, required=False):
"""
Instantiate and return a Django form field suitable for editing the property's value.
"""
field_kwargs = {
'label': self.title or title(name),
'help_text': self.description,
'required': required,
}
# String validation
if self.type == PropertyTypeEnum.STRING:
if self.minLength is not None:
field_kwargs['min_length'] = self.minLength
if self.maxLength is not None:
field_kwargs['max_length'] = self.maxLength
if self.pattern is not None:
field_kwargs['validators'] = [
RegexValidator(regex=self.pattern)
]
# Integer/number validation
elif self.type in (PropertyTypeEnum.INTEGER, PropertyTypeEnum.NUMBER):
if self.minimum:
field_kwargs['min_value'] = self.minimum
if self.maximum:
field_kwargs['min_value'] = self.maximum
if self.multipleOf:
field_kwargs['validators'] = [
MultipleOfValidator(multiple=self.multipleOf)
]
return self.field_class(**field_kwargs)
@property
def field_class(self):
"""
Resolve the property's type (and string format, if specified) to the appropriate field class.
"""
if self.type == PropertyTypeEnum.STRING.value and self.format is not None:
try:
return STRING_FORM_FIELDS[self.format]
except KeyError:
raise ValueError(f"Unsupported string format type: {self.format}")
try:
return FORM_FIELDS[self.type]
except KeyError:
raise ValueError(f"Unknown property type: {self.type}")