Skip to content

Commit

Permalink
🚧 Class extraction compilation initial approach
Browse files Browse the repository at this point in the history
  • Loading branch information
daquinteroflex committed Oct 18, 2024
1 parent 4b0d22a commit b6a8f6d
Show file tree
Hide file tree
Showing 18 changed files with 1,015 additions and 105 deletions.
11 changes: 11 additions & 0 deletions autoflex/constructors/field_info_to_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
This file contains all the functionality to convert from a compiled `AutoflexFieldInfo` or standard pydantic `FieldInfo`
into a AutoflexPropertyType. This way all the fields and annotations can be compiled into a standardized data type
"""
from autoflex.types import FieldTypes

def compile_field_into_property(
field: FieldTypes
):
"""
"""
4 changes: 2 additions & 2 deletions autoflex/constructors/parameter_table.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""
This file contains the structure of a ParameterTable.
"""
from autoflex.types import AutoflexBaseClass
from autoflex.types import PropertyTypes

def extract_class_to_parameter_table(physcial_parameter: PhysicalParameter) -> ParameterTable:
def extract_class_to_parameter_table(physcial_parameter: PropertyTypes) -> ParameterTable:
"""
This method converts from a given class or schema declaration into a container of data required for a ParameterTable
in its intended implementation.
Expand Down
127 changes: 116 additions & 11 deletions autoflex/extractors.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from pydantic import BaseModel

def determine_pydantic_version_from_base_model(model: BaseModel):
"""Determine if a BaseModel is from Pydantic v1 or v2."""
if hasattr(model, 'model_fields'):
return 2
elif hasattr(model, '__fields__'):
return 1
else:
raise ValueError("Unknown Pydantic version or incompatible BaseModel class.")
"""
Note that the compilation flow is as follows:
.. code::
FieldTypes -> PropertyTypes
PhysicalFieldInfo -> PhysicalProperty
pd.FieldInfo -> Property
def get_field_infos(model: BaseModel):
Then, this automated flow can be run for a given BaseModel and the properties can be extracted accordingly.
"""
import pydantic as pd
from autoflex.types import PropertyTypes, FieldTypes, PhysicalProperty, PhysicalFieldInfo, Property
from autoflex.version_manager import determine_pydantic_version_from_base_model

def get_field_infos(model: pd.BaseModel):
"""Get all FieldInfo instances from a Pydantic model, compatible with v1 and v2."""
version = determine_pydantic_version_from_base_model(model)

Expand All @@ -27,3 +30,105 @@ def get_field_infos(model: BaseModel):
field_infos.append(field)

return field_infos


def physical_field_info_to_physical_property(field: PhysicalFieldInfo, field_name: str) -> PhysicalProperty:
"""
Convert a PhysicalFieldInfo instance to a PhysicalProperty.
Args:
field: The PhysicalFieldInfo instance.
field_name: The name of the field.
Returns:
PhysicalProperty: The corresponding PhysicalProperty instance.
"""
# Extract unit and math information
unit = field.unit
math = field.math

# Extract other field attributes
description = field.description or ""
default = field.default if field.default is not None else ""

# Assuming 'types' can be inferred from the field's type
types = str(field.outer_type_) if hasattr(field, 'outer_type_') else ""

return PhysicalProperty(
name=field_name,
types=types,
description=description,
default=str(default),
unit=unit,
math=math
)


def field_info_to_property(field: pd.fields.FieldInfo, field_name: str) -> Property:
"""
Convert a standard FieldInfo instance to a Property.
Args:
field: The FieldInfo instance.
field_name: The name of the field.
Returns:
Property: The corresponding Property instance.
"""
description = field.description or ""
default = field.default if field.default is not None else ""
types = str(field.outer_type_) if hasattr(field, 'outer_type_') else ""

return Property(
name=field_name,
types=types,
description=description,
default=str(default)
)


def auto_field_to_property_type(field: FieldTypes, field_name: str) -> PropertyTypes:
"""
Convert a FieldTypes instance to a PropertyTypes instance.
Args:
field: The FieldTypes instance (PhysicalFieldInfo or FieldInfo).
field_name: The name of the field.
Returns:
PropertyTypes: The corresponding PropertyTypes instance.
"""
if isinstance(field, PhysicalFieldInfo):
return physical_field_info_to_physical_property(field, field_name)
else:
return field_info_to_property(field, field_name)


def extract_property_list_from_model(model: pd.BaseModel) -> list[PropertyTypes]:
"""
Extract a list of PropertyTypes from a Pydantic model.
Args:
model: The Pydantic BaseModel instance.
Returns:
List[PropertyTypes]: A list of PropertyTypes instances extracted from the model.
"""
field_infos = get_field_infos(model)
properties = []

# Get field names based on Pydantic version
version = determine_pydantic_version_from_base_model(model)
if version == 2:
field_names = list(model.model_fields.keys())
elif version == 1:
field_names = list(model.__fields__.keys())
else:
field_names = []

for field, field_name in zip(field_infos, field_names):
property_item = auto_field_to_property_type(field, field_name)
properties.append(property_item)

return properties

22 changes: 12 additions & 10 deletions autoflex/descriptors/field.py → autoflex/field.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
from dataclasses import field
from typing import Any, Optional
from pydantic import Field
from autoflex.types import AutoflexFieldInfo, AutoflexParameterTypes
from autoflex.types import PhysicalFieldInfo, UnitTypes, SymbolicTypes


def AutoflexField(
def PhysicalField(
default: Any = ...,
*,
autoflex_parameters: Optional[AutoflexParameterTypes] = None,
unit: UnitTypes,
math: SymbolicTypes,
**kwargs
) -> AutoflexFieldInfo:
) -> PhysicalFieldInfo:
"""
A wrapper around pydantic's Field function that returns an instance of AutoflexFieldInfo
instead of FieldInfo.
Args:
default: The default value of the field.
autoflex_parameters: An additional argument specific to AutoflexFieldInfo.
unit: The UnitType to represent.
math: The SymbolicType to represent.
**kwargs: Any other keyword arguments passed to pydantic's Field.
Returns:
AutoflexFieldInfo: Custom field info object.
"""

field_info = Field(default=default, **kwargs) # Call pydantic's Field internally
# Need to compile this into a FieldInfo in order to extract the correct kwargs.
field_info = Field(default=default, **kwargs)

# Return an instance of AutoflexFieldInfo instead of the default FieldInfo
return AutoflexFieldInfo(
return PhysicalFieldInfo(
default=default,
autoflex=autoflex_parameters,
**field_info.dict(exclude_none=True)
unit=unit,
math=math,
)
7 changes: 4 additions & 3 deletions autoflex/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from autoflex.types.parameters import PhysicalParameter, AutoflexParameterTypes
from autoflex.types.descriptors import Unit, Symbolic, SymbolicTypes
from autoflex.types.field import AutoflexFieldInfo
from autoflex.types.property import PhysicalProperty, Property, PropertyTypes
from autoflex.types.descriptors import Symbolic, SymbolicTypes, Unit, UnitTypes
from autoflex.types.field_info import PhysicalFieldInfo, FieldTypes
from autoflex.types.field_info import PhysicalFieldInfo
2 changes: 2 additions & 0 deletions autoflex/types/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ class Unit(AutoflexBaseModel):
name: str = Field(..., description="Name of the unit")
symbol: SymbolicTypes = Field(..., description="Symbol for the unit")
description: str = Field(None, description="Optional description of the unit")

UnitTypes = Union[str, Unit]
8 changes: 0 additions & 8 deletions autoflex/types/field.py

This file was deleted.

20 changes: 20 additions & 0 deletions autoflex/types/field_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import pydantic as pd
from autoflex.types.descriptors import UnitTypes, SymbolicTypes

class PhysicalFieldInfo(pd.fields.FieldInfo):
"""
Each field should correspond to an individual physical property field that can represent it completely within the documentation.
Note that this compiles into a PhysicalProperty accordingly.
"""

unit: UnitTypes = ""
"""
"""

math: SymbolicTypes = ""
"""
"""


FieldTypes = PhysicalFieldInfo | pd.fields.FieldInfo
21 changes: 14 additions & 7 deletions autoflex/types/parameters.py → autoflex/types/property.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
from pydantic import Field
from autoflex.types.core import AutoflexBaseModel
from autoflex.types.descriptors import Unit, SymbolicTypes
from autoflex.types.descriptors import UnitTypes, SymbolicTypes

class PhysicalParameter(AutoflexBaseModel):
class Property(AutoflexBaseModel):
"""
Contains all the information encoded in a standard property.
"""
name: str = ""
types: str = ""
description: str = ""
default: str = ""


class PhysicalProperty(Property):
"""
This structure instance is a representation of the relevant information that might want to represent in a parameter
row or in another container.
We need the parameter name, the type definition in a format we might want to represent (or even interact with)
a description which may be
"""
name: str = ""
types: str = ""
description: str = ""
math: SymbolicTypes = Field(..., description="The mathematical representation defining the physical parameter in raw string latex")
unit: Unit = Field(..., description="The unit of the physical parameter")
unit: UnitTypes = Field(..., description="The unit of the physical parameter")


AutoflexParameterTypes = PhysicalParameter
PropertyTypes = PhysicalProperty | Property
6 changes: 6 additions & 0 deletions autoflex/types/structures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from autoflex.types import PropertyTypes

PropertyCollectionTable = list[PropertyTypes]
"""
A property table can be compiled from a list of compiled AutoflexProperties in a standard data type format.
"""
10 changes: 10 additions & 0 deletions autoflex/version_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import pydantic as pd

def determine_pydantic_version_from_base_model(model: pd.BaseModel):
"""Determine if a BaseModel is from Pydantic v1 or v2."""
if hasattr(model, 'model_fields'):
return 2
elif hasattr(model, '__fields__'):
return 1
else:
raise ValueError("Unknown Pydantic version or incompatible BaseModel class.")
2 changes: 1 addition & 1 deletion demo/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .basic_class import BasicClass
from .basic_class import BasicClass, BasicMixedAnnotatedClass
8 changes: 6 additions & 2 deletions demo/basic_class.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pydantic
import pydantic as pd


class BasicClass(pydantic.BaseModel):
class BasicClass(pd.BaseModel):
my_parameter_1: int = 1
my_parameter_2: str = 2
my_parameter_3: float = 3.0
Expand All @@ -25,3 +25,7 @@ def my_static_method():
This is my static method.
"""
pass


class BasicMixedAnnotatedClass(BasicClass):
my_parameter_4: str = pd.Field("" , description="This is my field parameter.", json_schema_extra={})
5 changes: 5 additions & 0 deletions docs/api/descriptors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ Descriptors
Part of the goal of using dedicated ``AutoflexField`` definitions is that we can compile this data into a nice way
to visualise it both in the terminal and on the web. It can be used as a more helpful visualisation tool than the standard
type descriptions.

We want to provide the implementation of the Field functionality so that an `AutoflexField` compiles into a standard
Pydantic Field whilst using the ``json_schema_extra`` to encode the information accordingly.


5 changes: 5 additions & 0 deletions docs/api/fields.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@


.. code::
Loading

0 comments on commit b6a8f6d

Please sign in to comment.