Skip to content

Validazione con pydantic e pandera #13

Description

@xyffar
import pandera as pa
import pandas as pd
from pydantic import BaseModel, field_validator, Field
from pandera.typing import Series, DataFrame
from typing import List, Tuple
from datetime import datetime
import re

# Schema per la Series: valori float positivi
class PositiveFloatSeries(pa.SeriesModel):
    class Config:
        dtype = pa.Float
        checks = pa.Check.ge(0)
        index = pa.Index(pa.DateTime)

# Schema per il DataFrame: indice di tipo data e una colonna di serie
class DatedDataFrame(pa.DataFrameModel):
    valore_di_interesse: pa.Series[float] = pa.Field(nullable=True)
    eq_line: pa.Series[Series[PositiveFloatSeries]]

    class Config:
        index = pa.Index(datetime, name="data_ora", coerce=True)
        # La colonna 'valore_di_interesse' non deve contenere valori negativi
        checks = pa.Check.ge(0, column='valore_di_interesse')

# Modello per la validazione di ogni singolo dizionario
class ExtraDataModel(BaseModel):
    index: int = Field(gt=0)
    t_0: int = Field(gt=0)
    t_1: int
    date_0: datetime
    date_1: datetime

    @field_validator('t_1')
    @classmethod
    def validate_t_1(cls, v, info):
        if v <= info.data['t_0']:
            raise ValueError('t_1 deve essere maggiore di t_0')
        return v

    @field_validator('date_1')
    @classmethod
    def validate_date_1(cls, v, info):
        if v <= info.data['date_0']:
            raise ValueError('date_1 deve essere maggiore di date_0')
        return v

# Modello Pydantic che integra tutti gli schemi e le regole
class InputModel(BaseModel):
    my_int: int = Field(gt=0)
    my_float: float = Field(gt=0, lt=1)
    my_str: str
    my_list: List[int] = Field(min_length=17, max_length=17)
    my_tuple: Tuple[str, ...]
    my_series: Series[PositiveFloatSeries]
    my_dataframe: DataFrame[DatedDataFrame]
    
    # Lista di dizionari con le regole di validazione
    extra_data: List[ExtraDataModel]

    # Validatore per la stringa
    @field_validator('my_str')
    @classmethod
    def validate_my_str(cls, v):
        if not v:
            raise ValueError('La stringa non può essere vuota')
        if not re.match(r'^[a-zA-Z0-9.]+$', v):
            raise ValueError('La stringa può contenere solo lettere, numeri e il punto')
        return v
    
    # Validatore per la lista
    @field_validator('my_list')
    @classmethod
    def validate_my_list(cls, v):
        for item in v:
            if not (5 <= item <= 27):
                raise ValueError('Tutti i valori nella lista devono essere compresi tra 5 e 27')
        return v

def process_data(inputs: InputModel):
    print("Dati ricevuti e validati con successo.")
    print("---------------------------------")
    print("Informazioni sugli input:")
    print(f"Intero: {inputs.my_int}")
    print(f"Stringa: {inputs.my_str}")
    print(f"Serie Pandas:\n{inputs.my_series}")
    print(f"DataFrame Pandas:\n{inputs.my_dataframe}")
    print("\nInformazioni aggiuntive (lista di dizionari):")
    for i, data in enumerate(inputs.extra_data):
        print(f"  Dizionario {i+1}:")
        print(f"    Index: {data.index}")
        print(f"    t_0: {data.t_0}")
        print(f"    t_1: {data.t_1}")
        print(f"    date_0: {data.date_0}")
        print(f"    date_1: {data.date_1}")

# Esempio 1: Dati validi
valid_series = pd.Series([1.5, 2.0, 5.8], name='serie_valori')
valid_series_list = [
    pd.Series([10.1, 10.2], name='sub_series_1'),
    pd.Series([20.1, 20.2], name='sub_series_2')
]
valid_df = pd.DataFrame(
    {'valore_di_interesse': [10.5, 20.8],
     'eq_line': valid_series_list},
    index=pd.to_datetime(['2025-09-01', '2025-09-02'])
)

valid_extra_data_list = [
    {"index": 1, "t_0": 5, "t_1": 10, "date_0": datetime(2025, 1, 1), "date_1": datetime(2025, 1, 10)},
    {"index": 2, "t_0": 10, "t_1": 20, "date_0": datetime(2025, 2, 1), "date_1": datetime(2025, 2, 20)}
]

valid_data = {
    "my_int": 1,
    "my_float": 0.5,
    "my_str": 'testo.prova.123',
    "my_list": [10, 15, 20, 25, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
    "my_tuple": ('a', 'b'),
    "my_series": valid_series,
    "my_dataframe": valid_df,
    "extra_data": valid_extra_data_list
}

try:
    print("--- Tentativo con dati validi ---")
    valid_inputs = InputModel(**valid_data)
    process_data(valid_inputs)
except Exception as e:
    print(f"\nErrore inatteso: {e}")

print("\n\n" + "="*40 + "\n\n")

# Esempio 2: Dati non validi (valore negativo nella serie)
invalid_series_1 = pd.Series([1.5, -2.0, 5.8], name='serie_valori')
invalid_data_1 = {**valid_data, 'my_series': invalid_series_1}

try:
    print("--- Tentativo con dati non validi (serie) ---")
    InputModel(**invalid_data_1)
except Exception as e:
    print(f"\nErrore di validazione previsto:\n{e}")

print("\n\n" + "="*40 + "\n\n")

# Esempio 3: Dati non validi (t_1 non è maggiore di t_0 in uno dei dizionari)
invalid_extra_data_list = [
    {"index": 1, "t_0": 5, "t_1": 10, "date_0": datetime(2025, 1, 1), "date_1": datetime(2025, 1, 10)},
    {"index": 2, "t_0": 20, "t_1": 10, "date_0": datetime(2025, 2, 1), "date_1": datetime(2025, 2, 20)}  # t_1 < t_0
]
invalid_data_2 = {**valid_data, 'extra_data': invalid_extra_data_list}

try:
    print("--- Tentativo con dati non validi (lista di dizionari) ---")
    InputModel(**invalid_data_2)
except Exception as e:
    print(f"\nErrore di validazione previsto:\n{e}")

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions