Skip to content

Commit

Permalink
Deprecated flask pydantic models (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
signebedi committed Mar 24, 2024
1 parent 85fbafa commit f379520
Showing 1 changed file with 98 additions and 99 deletions.
197 changes: 98 additions & 99 deletions libreforms_fastapi/utils/pydantic_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,102 +188,6 @@ def generate_html_form(fields: dict) -> List[str]:

return form_html



def generate_pydantic_models(form_config: dict):
"""
Dynamically generates Pydantic models based on a specified form configuration. Each form is represented as a model,
with fields defined according to the configuration provided in `form_config`. This allows for the dynamic validation
of data according to administratively defined forms. Each field's type, default value, and optionality are considered
in the model creation process.
Parameters
----------
form_config : dict
A dictionary containing the form configurations, where each key is a form name and its value is another dictionary
mapping field names to their specifications. Each field specification must at least include 'output_type' for the
field's data type, and may optionally include a 'default' value. If a 'default' value is not provided, the field
is treated as optional.
Example:
{
"form_name": {
"field_name": {
"output_type": Type,
"default": Any, # Optional
},
...
},
...
}
Returns
-------
dict
A dictionary where each key is a form name and its value is a dynamically created Pydantic model class. These models
can then be used to validate data according to the defined form configurations.
Raises
------
TypeError
If an unrecognized type is provided in the form configuration, though this is primarily handled through Pydantic's
own type validation mechanisms.
Example Usage
-------------
form_config = {
"contact_form": {
"name": {
"output_type": str,
"default": "John Doe"
},
"age": {
"output_type": int,
# No default implies optional
},
...
}
}
models = generate_pydantic_models(form_config)
ContactFormModel = models["contact_form"]
form_data = {"name": "Jane Doe", "age": 30}
validated_data = ContactFormModel(**form_data)
A quick note
-----
The function dynamically sets fields as optional if no default value is provided, unless the field is explicitly
marked as `Optional[Type]`. It also allows for arbitrary types to be used within the models through the
`arbitrary_types_allowed` configuration.
This function is designed for use in applications where form fields and validation rules are configurable and
not known until runtime, providing flexibility in handling user submissions.
"""
models = {}

for form_name, fields in form_config.items():
field_definitions = {}

for field_name, field_info in fields.items():
python_type: Type = field_info["output_type"]
default = field_info.get("default", ...)

# Ensure Optional is always used with a specific type
if default is ... and python_type != Optional:
python_type = Optional[python_type]

field_definitions[field_name] = (python_type, default)

# Creating the model dynamically, allowing arbitrary types
class Config:
arbitrary_types_allowed = True

model = create_model(form_name, __config__=Config, **field_definitions)
models[form_name] = model

return models



def get_form_names(config_path=config.FORM_CONFIG_PATH):
"""
Given a form config path, return a list of available forms, defaulting to the example
Expand Down Expand Up @@ -347,7 +251,7 @@ def get_form_config(form_name, config_path=config.FORM_CONFIG_PATH, update=False
# But, it is a legitimate format for clients to pass either (1) data that only
# includes the changes the client wants to make or (2) all the data, changing only
# the fields the client wants to change. We need to be able to make sense of these
# two cases. See: https://github.com/signebedi/libreforms-fastapi/issues/34
# two cases. See: https://github.com/signebedi/libreforms-fastapi/issues/34.
if update:
field_definitions[field_name] = (Optional[python_type], None)
else:
Expand Down Expand Up @@ -379,7 +283,7 @@ class Config:

return model


# Deprecated
def __reconstruct_form_data(request, form_fields):
"""
This repackages request data into a format that pydantic will be able to understand.
Expand Down Expand Up @@ -413,4 +317,99 @@ def __reconstruct_form_data(request, form_fields):
if isinstance(reconstructed_form_data[field], list) and len(reconstructed_form_data[field]) == 1 and target_type != list:
reconstructed_form_data[field] = reconstructed_form_data[field][0]

return reconstructed_form_data
return reconstructed_form_data



# Deprecated
def __generate_pydantic_models(form_config: dict):
"""
Dynamically generates Pydantic models based on a specified form configuration. Each form is represented as a model,
with fields defined according to the configuration provided in `form_config`. This allows for the dynamic validation
of data according to administratively defined forms. Each field's type, default value, and optionality are considered
in the model creation process.
Parameters
----------
form_config : dict
A dictionary containing the form configurations, where each key is a form name and its value is another dictionary
mapping field names to their specifications. Each field specification must at least include 'output_type' for the
field's data type, and may optionally include a 'default' value. If a 'default' value is not provided, the field
is treated as optional.
Example:
{
"form_name": {
"field_name": {
"output_type": Type,
"default": Any, # Optional
},
...
},
...
}
Returns
-------
dict
A dictionary where each key is a form name and its value is a dynamically created Pydantic model class. These models
can then be used to validate data according to the defined form configurations.
Raises
------
TypeError
If an unrecognized type is provided in the form configuration, though this is primarily handled through Pydantic's
own type validation mechanisms.
Example Usage
-------------
form_config = {
"contact_form": {
"name": {
"output_type": str,
"default": "John Doe"
},
"age": {
"output_type": int,
# No default implies optional
},
...
}
}
models = generate_pydantic_models(form_config)
ContactFormModel = models["contact_form"]
form_data = {"name": "Jane Doe", "age": 30}
validated_data = ContactFormModel(**form_data)
A quick note
-----
The function dynamically sets fields as optional if no default value is provided, unless the field is explicitly
marked as `Optional[Type]`. It also allows for arbitrary types to be used within the models through the
`arbitrary_types_allowed` configuration.
This function is designed for use in applications where form fields and validation rules are configurable and
not known until runtime, providing flexibility in handling user submissions.
"""
models = {}

for form_name, fields in form_config.items():
field_definitions = {}

for field_name, field_info in fields.items():
python_type: Type = field_info["output_type"]
default = field_info.get("default", ...)

# Ensure Optional is always used with a specific type
if default is ... and python_type != Optional:
python_type = Optional[python_type]

field_definitions[field_name] = (python_type, default)

# Creating the model dynamically, allowing arbitrary types
class Config:
arbitrary_types_allowed = True

model = create_model(form_name, __config__=Config, **field_definitions)
models[form_name] = model

return models

0 comments on commit f379520

Please sign in to comment.