diff --git a/docs/api/bo4e.rst b/docs/api/bo4e.rst index 2f86c4a03..28ec82d21 100644 --- a/docs/api/bo4e.rst +++ b/docs/api/bo4e.rst @@ -10,6 +10,7 @@ Subpackages bo4e.bo bo4e.com bo4e.enum + bo4e.utils Submodules ---------- diff --git a/docs/uml.py b/docs/uml.py index 87a002dab..97d1dab45 100644 --- a/docs/uml.py +++ b/docs/uml.py @@ -14,25 +14,13 @@ import subprocess from abc import ABCMeta, abstractmethod from pathlib import Path -from re import Pattern from typing import Any, Dict, List, Optional, Tuple, Type, cast import networkx as nx # type: ignore[import] import requests # type: ignore[import] - -# pylint: disable=no-name-in-module -from pydantic import ConstrainedStr -from pydantic.fields import ( - MAPPING_LIKE_SHAPES, - SHAPE_GENERIC, - SHAPE_LIST, - SHAPE_NAME_LOOKUP, - SHAPE_SINGLETON, - SHAPE_TUPLE, - ModelField, -) -from pydantic.main import ModelMetaclass -from pydantic.typing import display_as_type +from pydantic._internal._model_construction import ModelMetaclass +from pydantic._internal._repr import display_as_type +from pydantic.fields import FieldInfo # pylint: disable=too-few-public-methods @@ -143,7 +131,7 @@ def add_association( self, node1: str, node2: str, - through_field: ModelField, + through_field: FieldInfo, card1: Optional[Cardinality] = None, card2: Optional[Cardinality] = None, ) -> None: @@ -228,37 +216,39 @@ def get_cardinality_string(card: Optional[Cardinality]) -> Optional[str]: return None @staticmethod - def model_field_str(model_field: ModelField, card: Optional[Cardinality] = None) -> str: + def model_field_str(model_field: FieldInfo, card: Optional[Cardinality] = None) -> str: """ - Parse the type of the ModelField to a printable string. Copied from pydantic.field.ModelField._type_display() + Parse the type of the ModelField to a printable string. Copied from + pydantic._internal._repr.display_as_type + https://github.com/pydantic/pydantic/blob/58ae1ef77a4bf4276aaa6214aaaaf59455f5e587/pydantic/_internal/_repr.py#L85 """ - result_str = display_as_type(model_field.type_) - + result_str = display_as_type(model_field.annotation) + # todo: check if this is still necessary + # https://github.com/bo4e/BO4E-python/issues/478 # have to do this since display_as_type(self.outer_type_) is different (and wrong) on python 3.6 - if model_field.shape in MAPPING_LIKE_SHAPES: - result_str = f"Mapping[{display_as_type(cast(ModelField, model_field.key_field).type_)}, {result_str}]" - elif model_field.shape == SHAPE_TUPLE: - result_str = "Tuple[" + ", ".join( - display_as_type( - sub_field.type_ for sub_field in model_field.sub_fields # type:ignore[arg-type,union-attr] - ) - ) - result_str += "]" - elif model_field.shape == SHAPE_GENERIC: - assert model_field.sub_fields - result_str = ( - f"{display_as_type(model_field.type_)}[" - f"{', '.join(display_as_type(sub_field.type_) for sub_field in model_field.sub_fields)}]" - ) - elif model_field.shape not in (SHAPE_SINGLETON, SHAPE_LIST): - result_str = SHAPE_NAME_LOOKUP[model_field.shape].format(result_str) - - if isinstance(model_field.outer_type_, type) and issubclass(model_field.outer_type_, ConstrainedStr): - if isinstance(model_field.outer_type_.regex, Pattern): - result_str = f"str<{model_field.outer_type_.regex.pattern}>" - elif isinstance(model_field.outer_type_.regex, str): - result_str = f"str<{model_field.outer_type_.regex}>" - + # if model_field.shape in MAPPING_LIKE_SHAPES: + # result_str = f"Mapping[{display_as_type(cast(ModelField, model_field.key_field).type_)}, {result_str}]" + # elif model_field.shape == SHAPE_TUPLE: + # result_str = "Tuple[" + ", ".join( + # display_as_type( + # sub_field.type_ for sub_field in model_field.sub_fields # type:ignore[arg-type,union-attr] + # ) + # ) + # result_str += "]" + # elif model_field.shape == SHAPE_GENERIC: + # assert model_field.sub_fields + # result_str = ( + # f"{display_as_type(model_field.type_)}[" + # f"{', '.join(display_as_type(sub_field.type_) for sub_field in model_field.sub_fields)}]" + # ) + # elif model_field.shape not in (SHAPE_SINGLETON, SHAPE_LIST): + # result_str = SHAPE_NAME_LOOKUP[model_field.shape].format(result_str) + # + # if is_constrained_str(model_field): + # if isinstance(model_field.outer_type_.regex, Pattern): + # result_str = f"str<{model_field.outer_type_.regex.pattern}>" + # elif isinstance(model_field.outer_type_.regex, str): + # result_str = f"str<{model_field.outer_type_.regex}>" assert card is not None return f"{result_str} [{_UMLNetworkABC.get_cardinality_string(card)}]" @@ -291,13 +281,14 @@ def _node_to_str(self, node: str, detailed: bool = True, **kwargs: Any) -> str: if detailed: cls_str += " {\n" for field_dict in self.nodes[node]["fields"].values(): - model_field = field_dict["model_field"] - type_modl_namespace = f"{model_field.type_.__module__}.{model_field.type_.__name__}" + model_field: FieldInfo = field_dict["model_field"] + assert model_field.annotation is not None + type_modl_namespace = f"{model_field.annotation.__module__}.{model_field.annotation.__name__}" if type_modl_namespace in self[node]: # Skip the fields which will appear as references in the graph continue type_str = _UMLNetworkABC.model_field_str(model_field, field_dict["card"]) - if model_field.required: + if model_field.is_required(): cls_str += f"\t{model_field.alias} : {type_str}\n" else: cls_str += f"\t{model_field.alias} : {type_str} = {model_field.default}\n" @@ -457,37 +448,37 @@ def write_class_umls(uml_network: _UMLNetworkABC, namespaces_to_parse: List[str] return path_list -def model_field_str(model_field: ModelField) -> str: +def model_field_str(model_field: FieldInfo) -> str: """ Parse the type of the ModelField to a printable string. Copied from pydantic.field.ModelField._type_display() """ - result_str = display_as_type(model_field.type_) - + result_str = display_as_type(model_field.annotation) + # todo: check if this is still necessary + # https://github.com/bo4e/BO4E-python/issues/478 # have to do this since display_as_type(self.outer_type_) is different (and wrong) on python 3.6 - if model_field.shape in MAPPING_LIKE_SHAPES: - result_str = f"Mapping[{display_as_type(cast(ModelField, model_field.key_field).type_)}, {result_str}]" - elif model_field.shape == SHAPE_TUPLE: - result_str = "Tuple[" + ", ".join( - display_as_type( - sub_field.type_ for sub_field in model_field.sub_fields # type:ignore[arg-type,union-attr] - ) - ) - result_str += "]" - elif model_field.shape == SHAPE_GENERIC: - assert model_field.sub_fields - result_str = ( - f"{display_as_type(model_field.type_)}[" - f"{', '.join(display_as_type(sub_field.type_) for sub_field in model_field.sub_fields)}]" - ) - elif model_field.shape != SHAPE_SINGLETON: - result_str = SHAPE_NAME_LOOKUP[model_field.shape].format(result_str) - - if model_field.allow_none and (model_field.shape != SHAPE_SINGLETON or not model_field.sub_fields): - result_str = f"Optional[{result_str}]" + # if model_field.shape in MAPPING_LIKE_SHAPES: + # result_str = f"Mapping[{display_as_type(cast(ModelField, model_field.key_field).type_)}, {result_str}]" + # elif model_field.shape == SHAPE_TUPLE: + # result_str = "Tuple[" + ", ".join( + # display_as_type( + # sub_field.type_ for sub_field in model_field.sub_fields # type:ignore[arg-type,union-attr] + # ) + # ) + # result_str += "]" + # elif model_field.shape == SHAPE_GENERIC: + # assert model_field.sub_fields + # result_str = ( + # f"{display_as_type(model_field.type_)}[" + # f"{', '.join(display_as_type(sub_field.type_) for sub_field in model_field.sub_fields)}]" + # ) + # elif model_field.shape != SHAPE_SINGLETON: + # result_str = SHAPE_NAME_LOOKUP[model_field.shape].format(result_str) + # if model_field.allow_none and (model_field.shape != SHAPE_SINGLETON or not model_field.sub_fields): + # result_str = f"Optional[{result_str}]" return result_str -def get_cardinality(model_field: ModelField) -> Cardinality: +def get_cardinality(model_field: FieldInfo) -> Cardinality: """ Determines the cardinality of a field. This field can either contain a reference to another node in the graph or be of another arbitrary type. @@ -500,10 +491,12 @@ def get_cardinality(model_field: ModelField) -> Cardinality: if type_str.startswith("List[") or type_str.startswith("Optional[List["): card1 = "0" card2 = "*" - if hasattr(model_field.outer_type_, "max_items") and model_field.outer_type_.max_items: - card2 = str(model_field.outer_type_.max_items) - if hasattr(model_field.outer_type_, "min_items") and model_field.outer_type_.min_items: - card1 = str(model_field.outer_type_.min_items) + # todo: re-add min_length / max_length interpretation + # https://github.com/bo4e/BO4E-python/issues/477 + # if hasattr(model_field.outer_type_, "max_items") and model_field.outer_type_.max_items: + # card2 = str(model_field.outer_type_.max_items) + # if hasattr(model_field.outer_type_, "min_items") and model_field.outer_type_.min_items: + # card1 = str(model_field.outer_type_.min_items) return card1, card2 @@ -558,14 +551,15 @@ def _recursive_add_class( # ------------------------------------------------------------------------------------------------------------------ # ------ determine references in fields which pass `regex_incl_network` and `regex_excl_network` ------------------- for field_dict in uml_network.nodes[modl_namespace]["fields"].values(): - model_field: ModelField = field_dict["model_field"] + model_field: FieldInfo = field_dict["model_field"] # Add cardinality information to the field field_card = get_cardinality(model_field) field_dict["card"] = field_card - type_modl_namespace = f"{model_field.type_.__module__}.{model_field.type_.__name__}" + assert model_field.annotation is not None + type_modl_namespace = f"{model_field.annotation.__module__}.{model_field.annotation.__name__}" if re.match(regex_incl_network, type_modl_namespace) and not re.match(regex_excl_network, type_modl_namespace): if not uml_network.has_node(type_modl_namespace): - _recursive_add_class(model_field.type_, type_modl_namespace, uml_network) + _recursive_add_class(model_field.annotation, type_modl_namespace, uml_network) # type:ignore[arg-type] uml_network.add_association( modl_namespace, diff --git a/json_schemas/generate_json_schemas.py b/json_schemas/generate_json_schemas.py index 8d3f5a640..c99373088 100644 --- a/json_schemas/generate_json_schemas.py +++ b/json_schemas/generate_json_schemas.py @@ -28,4 +28,4 @@ # this sanitizing step is necessary since python 3.11 definition["description"] = definition["description"].strip() with open(file_path, "w+", encoding="utf-8") as json_schema_file: - json_schema_file.write(json.dumps(schema_json_dict, ensure_ascii=False, indent=4)) + json_schema_file.write(json.dumps(schema_json_dict, indent=4)) diff --git a/requirements.in b/requirements.in index 0885c7ce0..8cb2e76d3 100644 --- a/requirements.in +++ b/requirements.in @@ -1,3 +1,3 @@ iso3166 -pydantic==1.* +pydantic>=2.0.0 pyhumps diff --git a/requirements.txt b/requirements.txt index 9584ee8f6..4db7cdbf9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,20 @@ # -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: # # pip-compile requirements.in # +annotated-types==0.5.0 + # via pydantic iso3166==2.1.1 # via -r requirements.in -pydantic==1.10.5 +pydantic==2.0.0 # via -r requirements.in +pydantic-core==2.0.1 + # via pydantic pyhumps==3.8.0 # via -r requirements.in -typing-extensions==4.2.0 - # via pydantic +typing-extensions==4.7.1 + # via + # pydantic + # pydantic-core diff --git a/setup.cfg b/setup.cfg index ede0ccf07..a288f30d0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ include_package_data = True python_requires = >=3.10 install_requires = iso3166 - pydantic==1.* + pydantic>=2.0.0 pyhumps [options.packages.find] diff --git a/src/bo4e/bo/angebot.py b/src/bo4e/bo/angebot.py index 561434707..7267cd159 100644 --- a/src/bo4e/bo/angebot.py +++ b/src/bo4e/bo/angebot.py @@ -38,7 +38,7 @@ class Angebot(Geschaeftsobjekt): bo_typ: BoTyp = BoTyp.ANGEBOT # required attributes #: Eindeutige Nummer des Angebotes - angebotsnummer: constr(strict=True, regex=r"^\d+$") # type: ignore[valid-type] + angebotsnummer: constr(strict=True, pattern=r"^\d+$") # type: ignore[valid-type] #: Erstellungsdatum des Angebots angebotsdatum: datetime #: Sparte, für die das Angebot abgegeben wird (Strom/Gas) @@ -48,7 +48,7 @@ class Angebot(Geschaeftsobjekt): #: Empfänger des Angebots angebotsnehmer: Geschaeftspartner - varianten: conlist(Angebotsvariante, min_items=1) # type: ignore[valid-type] + varianten: conlist(Angebotsvariante, min_length=1) # type: ignore[valid-type] """ Eine oder mehrere Varianten des Angebots mit den Angebotsteilen; Ein Angebot besteht mindestens aus einer Variante.""" diff --git a/src/bo4e/bo/ausschreibung.py b/src/bo4e/bo/ausschreibung.py index 7b73bcd93..fae43e133 100644 --- a/src/bo4e/bo/ausschreibung.py +++ b/src/bo4e/bo/ausschreibung.py @@ -60,7 +60,7 @@ class Ausschreibung(Geschaeftsobjekt): Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein """ #: Die einzelnen Lose, aus denen sich die Ausschreibung zusammensetzt - lose: conlist(Ausschreibungslos, min_items=1) # type: ignore[valid-type] + lose: conlist(Ausschreibungslos, min_length=1) # type: ignore[valid-type] # optional attributes #: Aufzählung der unterstützten Ausschreibungsportale diff --git a/src/bo4e/bo/energiemenge.py b/src/bo4e/bo/energiemenge.py index 7256412ac..4d98b56c8 100644 --- a/src/bo4e/bo/energiemenge.py +++ b/src/bo4e/bo/energiemenge.py @@ -35,5 +35,5 @@ class Energiemenge(Geschaeftsobjekt): lokationstyp: Lokationstyp #: Gibt den Verbrauch in einer Zeiteinheit an - energieverbrauch: conlist(Verbrauch, min_items=1) # type: ignore[valid-type] + energieverbrauch: conlist(Verbrauch, min_length=1) # type: ignore[valid-type] # there are no optional attributes diff --git a/src/bo4e/bo/geschaeftsobjekt.py b/src/bo4e/bo/geschaeftsobjekt.py index c0338d00c..8d43f643a 100644 --- a/src/bo4e/bo/geschaeftsobjekt.py +++ b/src/bo4e/bo/geschaeftsobjekt.py @@ -43,6 +43,6 @@ class Config: """ alias_generator = camelize - allow_population_by_field_name = True + populate_by_name = True extra = "allow" json_encoders = {Decimal: str} diff --git a/src/bo4e/bo/kosten.py b/src/bo4e/bo/kosten.py index d48071ef8..3725d7568 100644 --- a/src/bo4e/bo/kosten.py +++ b/src/bo4e/bo/kosten.py @@ -36,7 +36,7 @@ class Kosten(Geschaeftsobjekt): #: Für diesen Zeitraum wurden die Kosten ermittelt gueltigkeit: Zeitraum #: In Kostenblöcken werden Kostenpositionen zusammengefasst. Beispiele: Netzkosten, Umlagen, Steuern etc - kostenbloecke: conlist(Kostenblock, min_items=1) # type: ignore[valid-type] + kostenbloecke: conlist(Kostenblock, min_length=1) # type: ignore[valid-type] # optional attributes #: Die Gesamtsumme über alle Kostenblöcke und -positionen diff --git a/src/bo4e/bo/lastgang.py b/src/bo4e/bo/lastgang.py index ab616be87..f08f0c49f 100644 --- a/src/bo4e/bo/lastgang.py +++ b/src/bo4e/bo/lastgang.py @@ -42,7 +42,7 @@ class _LastgangBody(Geschaeftsobjekt): #: Versionsnummer des Lastgangs version: Optional[str] = None #: Die OBIS-Kennzahl für den Wert, die festlegt, welche Größe mit dem Stand gemeldet wird, z.B. '1-0:1.8.1' - obis_kennzahl: Optional[constr(strict=True, regex=OBIS_PATTERN)] = None # type: ignore[valid-type] + obis_kennzahl: Optional[constr(strict=True, pattern=OBIS_PATTERN)] = None # type: ignore[valid-type] # pylint: disable=too-many-instance-attributes, too-few-public-methods @@ -89,4 +89,4 @@ class Lastgang(_LastgangBody): bo_typ: BoTyp = BoTyp.LASTGANG #: Die im Lastgang enthaltenen Messwerte - werte: conlist(Zeitreihenwert, min_items=1) # type: ignore[valid-type] + werte: conlist(Zeitreihenwert, min_length=1) # type: ignore[valid-type] diff --git a/src/bo4e/bo/marktlokation.py b/src/bo4e/bo/marktlokation.py index ed8eaec3b..b206be416 100644 --- a/src/bo4e/bo/marktlokation.py +++ b/src/bo4e/bo/marktlokation.py @@ -4,10 +4,11 @@ """ # pylint: disable=too-many-instance-attributes, too-few-public-methods -from typing import Any, Dict, Optional +from typing import Optional # pylint: disable=no-name-in-module -from pydantic import conlist, validator +from pydantic import conlist, field_validator +from pydantic_core.core_schema import ValidationInfo from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt from bo4e.bo.geschaeftspartner import Geschaeftspartner @@ -44,7 +45,7 @@ class Marktlokation(Geschaeftsobjekt): bo_typ: BoTyp = BoTyp.MARKTLOKATION #: Identifikationsnummer einer Marktlokation, an der Energie entweder verbraucht, oder erzeugt wird. marktlokations_id: str - _marktlokations_id_check = validator("marktlokations_id", allow_reuse=True)(validate_marktlokations_id) + _marktlokations_id_check = field_validator("marktlokations_id")(validate_marktlokations_id) #: Sparte der Marktlokation, z.B. Gas oder Strom sparte: Sparte #: Kennzeichnung, ob Energie eingespeist oder entnommen (ausgespeist) wird @@ -114,15 +115,16 @@ class Marktlokation(Geschaeftsobjekt): Flurstück erfolgen. """ - kundengruppen: conlist(Kundentyp, min_items=0) = None # type: ignore[valid-type] + kundengruppen: Optional[conlist(Kundentyp, min_length=0)] = None # type: ignore[valid-type] #: Kundengruppen der Marktlokation # pylint:disable=unused-argument, no-self-argument - @validator("katasterinformation", always=True) + @field_validator("katasterinformation") def validate_address_info( - cls, katasterinformation: Optional[Katasteradresse], values: Dict[str, Any] + cls, katasterinformation: Optional[Katasteradresse], validation_info: ValidationInfo ) -> Optional[Katasteradresse]: """Checks that there is one and only one valid adress given.""" + values = validation_info.data # type:ignore[attr-defined] all_address_attributes = [ values["lokationsadresse"], values["geoadresse"], diff --git a/src/bo4e/bo/marktteilnehmer.py b/src/bo4e/bo/marktteilnehmer.py index aae1d5770..5786d7892 100644 --- a/src/bo4e/bo/marktteilnehmer.py +++ b/src/bo4e/bo/marktteilnehmer.py @@ -35,7 +35,7 @@ class Marktteilnehmer(Geschaeftspartner): #: Gibt im Klartext die Bezeichnung der Marktrolle an marktrolle: Marktrolle #: Gibt die Codenummer der Marktrolle an - rollencodenummer: constr(strict=True, regex=r"^\d{13}$") # type: ignore[valid-type] + rollencodenummer: constr(strict=True, pattern=r"^\d{13}$") # type: ignore[valid-type] #: Gibt den Typ des Codes an rollencodetyp: Rollencodetyp #: Sparte des Marktteilnehmers, z.B. Gas oder Strom diff --git a/src/bo4e/bo/messlokation.py b/src/bo4e/bo/messlokation.py index 4c16403fe..c25e28a37 100644 --- a/src/bo4e/bo/messlokation.py +++ b/src/bo4e/bo/messlokation.py @@ -94,7 +94,7 @@ class Messlokation(Geschaeftsobjekt): """ # pylint: disable=unused-argument, no-self-argument - @validator("messlokations_id", always=True) + @validator("messlokations_id") def _validate_messlokations_id(cls, messlokations_id: str) -> str: if not messlokations_id: raise ValueError("The messlokations_id must not be empty.") @@ -105,7 +105,7 @@ def _validate_messlokations_id(cls, messlokations_id: str) -> str: return messlokations_id # pylint: disable=no-self-argument - @validator("katasterinformation", always=True) + @validator("katasterinformation") def validate_address_info( cls, katasterinformation: Optional[Katasteradresse], values: Dict[str, Any] ) -> Optional[Katasteradresse]: @@ -121,7 +121,7 @@ def validate_address_info( return katasterinformation # pylint: disable=no-self-argument - @validator("grundzustaendiger_msbim_codenr", always=True) + @validator("grundzustaendiger_msbim_codenr") def validate_grundzustaendiger_x_codenr( cls, grundzustaendiger_msbim_codenr: Optional[str], values: Dict[str, Any] ) -> Optional[str]: diff --git a/src/bo4e/bo/netznutzungsrechnung.py b/src/bo4e/bo/netznutzungsrechnung.py index 51f6ef487..db3d644d5 100644 --- a/src/bo4e/bo/netznutzungsrechnung.py +++ b/src/bo4e/bo/netznutzungsrechnung.py @@ -32,12 +32,12 @@ class Netznutzungsrechnung(Rechnung): bo_typ: BoTyp = BoTyp.NETZNUTZUNGSRECHNUNG #: Sparte (Strom, Gas ...) für die die Rechnung ausgestellt ist sparte: Sparte - absendercodenummer: constr(strict=True, regex=r"^\d{13}$") # type: ignore[valid-type] + absendercodenummer: constr(strict=True, pattern=r"^\d{13}$") # type: ignore[valid-type] """ Die Rollencodenummer des Absenders (siehe :class:`Marktteilnehmer`). Über die Nummer können weitere Informationen zum Marktteilnehmer ermittelt werden. """ - empfaengercodenummer: constr(strict=True, regex=r"^\d{13}$") # type: ignore[valid-type] + empfaengercodenummer: constr(strict=True, pattern=r"^\d{13}$") # type: ignore[valid-type] """ Die Rollencodenummer des Empfängers (siehe :class:`Marktteilnehmer`). Über die Nummer können weitere Informationen zum Marktteilnehmer ermittelt werden. diff --git a/src/bo4e/bo/preisblatt.py b/src/bo4e/bo/preisblatt.py index 7bc60e8b9..0f3a0308b 100644 --- a/src/bo4e/bo/preisblatt.py +++ b/src/bo4e/bo/preisblatt.py @@ -47,7 +47,7 @@ class Preisblatt(Geschaeftsobjekt): #: Der Zeitraum für den der Preis festgelegt ist gueltigkeit: Zeitraum #: Die einzelnen Positionen, die mit dem Preisblatt abgerechnet werden können. Z.B. Arbeitspreis, Grundpreis etc - preispositionen: conlist(Preisposition, min_items=1) # type: ignore[valid-type] + preispositionen: conlist(Preisposition, min_length=1) # type: ignore[valid-type] # optional attributes #: Der Netzbetreiber, der die Preise veröffentlicht hat herausgeber: Optional[Marktteilnehmer] = None diff --git a/src/bo4e/bo/region.py b/src/bo4e/bo/region.py index 64160fb7d..3a62283f1 100644 --- a/src/bo4e/bo/region.py +++ b/src/bo4e/bo/region.py @@ -31,7 +31,7 @@ class Region(Geschaeftsobjekt): bezeichnung: str #: Positivliste der Kriterien zur Definition der Region - positiv_liste: conlist(Regionskriterium, min_items=1) # type: ignore[valid-type] + positiv_liste: conlist(Regionskriterium, min_length=1) # type: ignore[valid-type] # optional attributes #: Negativliste der Kriterien zur Definition der Region diff --git a/src/bo4e/bo/regionaltarif.py b/src/bo4e/bo/regionaltarif.py index 14153a516..9d544d473 100644 --- a/src/bo4e/bo/regionaltarif.py +++ b/src/bo4e/bo/regionaltarif.py @@ -38,7 +38,7 @@ class Regionaltarif(Tarifinfo): #: Für die Berechnung der Kosten sind die hier abgebildeten Parameter heranzuziehen berechnungsparameter: Tarifberechnungsparameter #: Die festgelegten Preise mit regionaler Eingrenzung, z.B. für Arbeitspreis, Grundpreis etc. - tarifpreise: conlist(RegionaleTarifpreisposition, min_items=1) # type: ignore[valid-type] + tarifpreise: conlist(RegionaleTarifpreisposition, min_length=1) # type: ignore[valid-type] # optional attributes #: Auf- und Abschläge auf die Preise oder Kosten mit regionaler Eingrenzung diff --git a/src/bo4e/bo/standorteigenschaften.py b/src/bo4e/bo/standorteigenschaften.py index c72f921cd..245fc5301 100644 --- a/src/bo4e/bo/standorteigenschaften.py +++ b/src/bo4e/bo/standorteigenschaften.py @@ -31,7 +31,7 @@ class Standorteigenschaften(Geschaeftsobjekt): # required attributes bo_typ: BoTyp = BoTyp.STANDORTEIGENSCHAFTEN #: Eigenschaften zur Sparte Strom - eigenschaften_strom: conlist(StandorteigenschaftenStrom, min_items=1) # type: ignore[valid-type] + eigenschaften_strom: conlist(StandorteigenschaftenStrom, min_length=1) # type: ignore[valid-type] # optional attributes #: Eigenschaften zur Sparte Gas diff --git a/src/bo4e/bo/tarif.py b/src/bo4e/bo/tarif.py index 803e57db2..b08a8c074 100644 --- a/src/bo4e/bo/tarif.py +++ b/src/bo4e/bo/tarif.py @@ -38,7 +38,7 @@ class Tarif(Tarifinfo): #: Für die Berechnung der Kosten sind die hier abgebildeten Parameter heranzuziehen berechnungsparameter: Tarifberechnungsparameter #: Die festgelegten Preise mit regionaler Eingrenzung z.B. für Arbeitspreis, Grundpreis etc. - tarifpreise: conlist(TarifpreispositionProOrt, min_items=1) # type: ignore[valid-type] + tarifpreise: conlist(TarifpreispositionProOrt, min_length=1) # type: ignore[valid-type] # optional attributes #: Auf- und Abschläge auf die Preise oder Kosten mit regionaler Eingrenzung diff --git a/src/bo4e/bo/tarifinfo.py b/src/bo4e/bo/tarifinfo.py index 19722591d..5ee803393 100644 --- a/src/bo4e/bo/tarifinfo.py +++ b/src/bo4e/bo/tarifinfo.py @@ -46,13 +46,13 @@ class Tarifinfo(Geschaeftsobjekt): #: Strom oder Gas, etc. sparte: Sparte #: Kundentypen für den der Tarif gilt, z.B. Privatkunden - kundentypen: conlist(Kundentyp, min_items=1) # type: ignore[valid-type] + kundentypen: conlist(Kundentyp, min_length=1) # type: ignore[valid-type] #: Die Art des Tarifes, z.B. Eintarif oder Mehrtarif tarifart: Tarifart #: Hinweis auf den Tariftyp, z.B. Grundversorgung oder Sondertarif tariftyp: Tariftyp #: Weitere Merkmale des Tarifs, z.B. Festpreis oder Vorkasse - tarifmerkmale: conlist(Tarifmerkmal, min_items=1) # type: ignore[valid-type] + tarifmerkmale: conlist(Tarifmerkmal, min_length=1) # type: ignore[valid-type] #: Der Marktteilnehmer (Lieferant), der diesen Tarif anbietet anbieter: Marktteilnehmer diff --git a/src/bo4e/bo/tarifpreisblatt.py b/src/bo4e/bo/tarifpreisblatt.py index 9f6b5fd5d..c941e51e4 100644 --- a/src/bo4e/bo/tarifpreisblatt.py +++ b/src/bo4e/bo/tarifpreisblatt.py @@ -35,7 +35,7 @@ class Tarifpreisblatt(Tarifinfo): #: Gibt an, wann der Preis zuletzt angepasst wurde preisstand: datetime #: Die festgelegten Preise, z.B. für Arbeitspreis, Grundpreis etc. - tarifpreise: conlist(Tarifpreisposition, min_items=1) # type: ignore[valid-type] + tarifpreise: conlist(Tarifpreisposition, min_length=1) # type: ignore[valid-type] #: Für die Berechnung der Kosten sind die hier abgebildeten Parameter heranzuziehen berechnungsparameter: Tarifberechnungsparameter diff --git a/src/bo4e/bo/vertrag.py b/src/bo4e/bo/vertrag.py index cd946695d..fbd5c81cc 100644 --- a/src/bo4e/bo/vertrag.py +++ b/src/bo4e/bo/vertrag.py @@ -64,7 +64,7 @@ class Vertrag(Geschaeftsobjekt): In der Regel der Empfänger des Vertrags. Beispiel "Vertrag zwischen Vertragspartner 1 und Vertragspartner 2". """ - vertragsteile: conlist(Vertragsteil, min_items=1) # type: ignore[valid-type] + vertragsteile: conlist(Vertragsteil, min_length=1) # type: ignore[valid-type] """ Der Vertragsteil wird dazu verwendet, eine vertragliche Leistung in Bezug zu einer Lokation (Markt- oder Messlokation) festzulegen. diff --git a/src/bo4e/bo/zaehler.py b/src/bo4e/bo/zaehler.py index 942fcffa2..0ef215d46 100644 --- a/src/bo4e/bo/zaehler.py +++ b/src/bo4e/bo/zaehler.py @@ -42,7 +42,7 @@ class Zaehler(Geschaeftsobjekt): sparte: Sparte #: Strom oder Gas zaehlerauspraegung: Zaehlerauspraegung #: Spezifikation die Richtung des Zählers betreffend zaehlertyp: Zaehlertyp #: Typisierung des Zählers - zaehlwerke: conlist(Zaehlwerk, min_items=1) # type: ignore[valid-type] #: Die Zählwerke des Zählers + zaehlwerke: conlist(Zaehlwerk, min_length=1) # type: ignore[valid-type] #: Die Zählwerke des Zählers tarifart: Tarifart #: Spezifikation bezüglich unterstützter Tarifarten # optional attributes diff --git a/src/bo4e/bo/zeitreihe.py b/src/bo4e/bo/zeitreihe.py index 383c80b88..9929f9345 100644 --- a/src/bo4e/bo/zeitreihe.py +++ b/src/bo4e/bo/zeitreihe.py @@ -45,7 +45,7 @@ class Zeitreihe(Geschaeftsobjekt): #: Alle Werte in der Tabelle haben die Einheit, die hier angegeben ist einheit: Mengeneinheit #: Hier liegen jeweils die Werte - werte: conlist(Zeitreihenwert, min_items=1) # type: ignore[valid-type] + werte: conlist(Zeitreihenwert, min_length=1) # type: ignore[valid-type] # optional attributes #: Beschreibt die Verwendung der Zeitreihe diff --git a/src/bo4e/com/adresse.py b/src/bo4e/com/adresse.py index baa8706ff..de7c5cd54 100644 --- a/src/bo4e/com/adresse.py +++ b/src/bo4e/com/adresse.py @@ -48,7 +48,7 @@ class Adresse(COM): landescode: Landescode = Landescode.DE # type:ignore # pylint: disable=no-self-argument - @validator("postfach", always=True) + @validator("postfach") def strasse_xor_postfach(cls, postfach: Optional[str], values: Dict[str, Any]) -> Optional[str]: """ An address is valid if it contains a postfach XOR (a strasse AND hausnummer). diff --git a/src/bo4e/com/angebotsteil.py b/src/bo4e/com/angebotsteil.py index 1b5ac8bf1..ee5e65262 100644 --- a/src/bo4e/com/angebotsteil.py +++ b/src/bo4e/com/angebotsteil.py @@ -36,7 +36,7 @@ class Angebotsteil(COM): # required attributes #: Einzelne Positionen, die zu diesem Angebotsteil gehören - positionen: conlist(Angebotsposition, min_items=1) # type: ignore[valid-type] + positionen: conlist(Angebotsposition, min_length=1) # type: ignore[valid-type] # optional attributes #: Identifizierung eines Subkapitels einer Anfrage, beispielsweise das Los einer Ausschreibung diff --git a/src/bo4e/com/angebotsvariante.py b/src/bo4e/com/angebotsvariante.py index 8ecbff365..a5f9b465d 100644 --- a/src/bo4e/com/angebotsvariante.py +++ b/src/bo4e/com/angebotsvariante.py @@ -39,7 +39,7 @@ class Angebotsvariante(COM): #: Bis zu diesem Zeitpunkt gilt die Angebotsvariante bindefrist: datetime - teile: conlist(Angebotsteil, min_items=1) # type: ignore[valid-type] + teile: conlist(Angebotsteil, min_length=1) # type: ignore[valid-type] """ Angebotsteile werden im einfachsten Fall für eine Marktlokation oder Lieferstellenadresse erzeugt. Hier werden die Mengen und Gesamtkosten aller Angebotspositionen zusammengefasst. diff --git a/src/bo4e/com/aufabschlag.py b/src/bo4e/com/aufabschlag.py index d8401ef76..54c82715b 100644 --- a/src/bo4e/com/aufabschlag.py +++ b/src/bo4e/com/aufabschlag.py @@ -5,7 +5,7 @@ from typing import List, Optional -from pydantic import validator +from pydantic import field_validator from bo4e.com.com import COM from bo4e.com.preisstaffel import Preisstaffel @@ -46,7 +46,7 @@ class AufAbschlag(COM): #: Diesem Preis oder den Kosten ist der Auf/Abschlag zugeordnet. Z.B. Arbeitspreis, Gesamtpreis etc.. auf_abschlagsziel: Optional[AufAbschlagsziel] = None einheit: Optional[Waehrungseinheit] = None - _einheit_check = validator("einheit", allow_reuse=True)(einheit_only_for_abschlagstyp_absolut) + _einheit_check = field_validator("einheit")(einheit_only_for_abschlagstyp_absolut) """ Gibt an in welcher Währungseinheit der Auf/Abschlag berechnet wird. Euro oder Ct.. (Nur im Falle absoluter Aufschlagstypen). """ #: Internetseite, auf der die Informationen zum Auf-/Abschlag veröffentlicht sind. diff --git a/src/bo4e/com/aufabschlagproort.py b/src/bo4e/com/aufabschlagproort.py index ed464fe49..b797bb87d 100644 --- a/src/bo4e/com/aufabschlagproort.py +++ b/src/bo4e/com/aufabschlagproort.py @@ -33,4 +33,4 @@ class AufAbschlagProOrt(COM): #: Die ene't-Netznummer des Netzes in dem der Aufschlag gilt. netznr: str #: Werte für die gestaffelten Auf/Abschläge mit regionaler Eingrenzung. - staffeln: conlist(AufAbschlagstaffelProOrt, min_items=1) # type: ignore[valid-type] + staffeln: conlist(AufAbschlagstaffelProOrt, min_length=1) # type: ignore[valid-type] diff --git a/src/bo4e/com/aufabschlagregional.py b/src/bo4e/com/aufabschlagregional.py index 7dc8d807d..5eeabad59 100644 --- a/src/bo4e/com/aufabschlagregional.py +++ b/src/bo4e/com/aufabschlagregional.py @@ -41,7 +41,7 @@ class AufAbschlagRegional(COM): #: Bezeichnung des Auf-/Abschlags bezeichnung: str #: Werte für die gestaffelten Auf/Abschläge mit regionaler Eingrenzung - betraege: conlist(AufAbschlagProOrt, min_items=1) # type: ignore[valid-type] + betraege: conlist(AufAbschlagProOrt, min_length=1) # type: ignore[valid-type] # optional attributes #: Beschreibung zum Auf-/Abschlag diff --git a/src/bo4e/com/ausschreibungslos.py b/src/bo4e/com/ausschreibungslos.py index 10292b980..69ae3379c 100644 --- a/src/bo4e/com/ausschreibungslos.py +++ b/src/bo4e/com/ausschreibungslos.py @@ -51,7 +51,7 @@ class Ausschreibungslos(COM): anzahl_lieferstellen: int #: Die ausgeschriebenen Lieferstellen - lieferstellen: conlist(Ausschreibungsdetail, min_items=1) # type: ignore[valid-type] + lieferstellen: conlist(Ausschreibungsdetail, min_length=1) # type: ignore[valid-type] #: Zeitraum, für den die in diesem Los enthaltenen Lieferstellen beliefert werden sollen lieferzeitraum: Zeitraum diff --git a/src/bo4e/com/com.py b/src/bo4e/com/com.py index 52c086ad8..d9a7367d5 100644 --- a/src/bo4e/com/com.py +++ b/src/bo4e/com/com.py @@ -33,7 +33,7 @@ class Config: """ alias_generator = camelize - allow_population_by_field_name = True + populate_by_name = True extra = "allow" json_encoders = {Decimal: str} diff --git a/src/bo4e/com/energiemix.py b/src/bo4e/com/energiemix.py index 6cd1c5a82..e583239b0 100644 --- a/src/bo4e/com/energiemix.py +++ b/src/bo4e/com/energiemix.py @@ -40,7 +40,7 @@ class Energiemix(COM): #: Jahr, für das der Energiemix gilt gueltigkeitsjahr: int #: Anteile der jeweiligen Erzeugungsart - anteil: conlist(Energieherkunft, min_items=1) # type: ignore[valid-type] + anteil: conlist(Energieherkunft, min_length=1) # type: ignore[valid-type] # optional attributes #: Bemerkung zum Energiemix diff --git a/src/bo4e/com/kostenposition.py b/src/bo4e/com/kostenposition.py index c8f82c79c..13b25a3f9 100644 --- a/src/bo4e/com/kostenposition.py +++ b/src/bo4e/com/kostenposition.py @@ -2,10 +2,11 @@ Contains Kostenposition and corresponding marshmallow schema for de-/serialization """ from datetime import datetime -from typing import Any, Dict, Optional +from typing import Optional # pylint: disable=too-few-public-methods, too-many-instance-attributes -from pydantic import validator +from pydantic import field_validator +from pydantic_core.core_schema import ValidationInfo from bo4e.com.betrag import Betrag from bo4e.com.com import COM @@ -47,7 +48,7 @@ class Kostenposition(COM): von: Optional[datetime] = None #: exklusiver bis-Zeitpunkt der Kostenzeitscheibe bis: Optional[datetime] = None - _bis_check = validator("bis", always=True, allow_reuse=True)(check_bis_is_later_than_von) + _bis_check = field_validator("bis")(check_bis_is_later_than_von) #: Die Menge, die in die Kostenberechnung eingeflossen ist. Beispiel: 3.660 kWh menge: Optional[Menge] = None @@ -62,8 +63,8 @@ class Kostenposition(COM): artikeldetail: Optional[str] = None @staticmethod - def _get_inclusive_start(values: Dict[str, Any]) -> Optional[datetime]: - return values["von"] + def _get_inclusive_start(values: ValidationInfo) -> Optional[datetime]: + return values.data["von"] # type:ignore[attr-defined] # @staticmethod # def _get_exclusive_end(values) -> Optional[datetime]: diff --git a/src/bo4e/com/preisposition.py b/src/bo4e/com/preisposition.py index 307c33c2a..ca31940c9 100644 --- a/src/bo4e/com/preisposition.py +++ b/src/bo4e/com/preisposition.py @@ -47,7 +47,7 @@ class Preisposition(COM): #: Hier wird festgelegt, auf welche Bezugsgrösse sich der Preis bezieht, z.B. kWh oder Stück bezugsgroesse: Mengeneinheit #: Preisstaffeln, die zu dieser Preisposition gehören - preisstaffeln: conlist(Preisstaffel, min_items=1) # type: ignore[valid-type] + preisstaffeln: conlist(Preisstaffel, min_length=1) # type: ignore[valid-type] # optional attributes zeitbasis: Optional[Zeiteinheit] = None diff --git a/src/bo4e/com/rechnungsposition.py b/src/bo4e/com/rechnungsposition.py index fc95126c1..5b40a6c3e 100644 --- a/src/bo4e/com/rechnungsposition.py +++ b/src/bo4e/com/rechnungsposition.py @@ -4,9 +4,10 @@ from datetime import datetime # pylint: disable=too-few-public-methods, too-many-instance-attributes -from typing import Any, Dict, Optional +from typing import Optional -from pydantic import validator +from pydantic import field_validator +from pydantic_core.core_schema import ValidationInfo from bo4e.com.betrag import Betrag from bo4e.com.com import COM @@ -38,7 +39,7 @@ class Rechnungsposition(COM): lieferung_von: datetime #: Start der Lieferung für die abgerechnete Leistung (inklusiv) lieferung_bis: datetime #: Ende der Lieferung für die abgerechnete Leistung (exklusiv) - _bis_check = validator("lieferung_bis", always=True, allow_reuse=True)(check_bis_is_later_than_von) + _bis_check = field_validator("lieferung_bis")(check_bis_is_later_than_von) #: Bezeichung für die abgerechnete Position positionstext: str @@ -81,9 +82,9 @@ class Rechnungsposition(COM): artikel_id: Optional[str] = None @staticmethod - def _get_inclusive_start(values: Dict[str, Any]) -> datetime: + def _get_inclusive_start(values: ValidationInfo) -> datetime: """return the inclusive start (used in the validator)""" - return values["lieferung_von"] + return values.data["lieferung_von"] # type:ignore[attr-defined] # def _get_exclusive_end(self) -> datetime: # """return the exclusive end (used in the validator)""" diff --git a/src/bo4e/com/regionalegueltigkeit.py b/src/bo4e/com/regionalegueltigkeit.py index 87835ad9f..4fbfe3a98 100644 --- a/src/bo4e/com/regionalegueltigkeit.py +++ b/src/bo4e/com/regionalegueltigkeit.py @@ -28,5 +28,5 @@ class RegionaleGueltigkeit(COM): # required attributes gueltigkeitstyp: Gueltigkeitstyp #: Unterscheidung ob Positivliste oder Negativliste übertragen wird kriteriums_werte: conlist( # type: ignore[valid-type] - KriteriumWert, min_items=1 + KriteriumWert, min_length=1 ) #: Hier stehen die Kriterien, die die regionale Gültigkeit festlegen diff --git a/src/bo4e/com/regionaleraufabschlag.py b/src/bo4e/com/regionaleraufabschlag.py index e4c44686e..6c83bc478 100644 --- a/src/bo4e/com/regionaleraufabschlag.py +++ b/src/bo4e/com/regionaleraufabschlag.py @@ -42,7 +42,7 @@ class RegionalerAufAbschlag(COM): bezeichnung: str #: Werte für die gestaffelten Auf/Abschläge mit regionaler Eingrenzung - staffeln: conlist(RegionalePreisstaffel, min_items=1) # type: ignore[valid-type] + staffeln: conlist(RegionalePreisstaffel, min_length=1) # type: ignore[valid-type] # optional attributes #: Beschreibung des Auf-/Abschlags diff --git a/src/bo4e/com/regionaletarifpreisposition.py b/src/bo4e/com/regionaletarifpreisposition.py index 9bb456f86..8c30d4ef3 100644 --- a/src/bo4e/com/regionaletarifpreisposition.py +++ b/src/bo4e/com/regionaletarifpreisposition.py @@ -37,7 +37,7 @@ class RegionaleTarifpreisposition(COM): #: Größe, auf die sich die Einheit bezieht, beispielsweise kWh, Jahr bezugseinheit: Mengeneinheit #: Hier sind die Staffeln mit ihren Preisangaben und regionalen Gültigkeiten definiert - preisstaffeln: conlist(RegionalePreisstaffel, min_items=1) # type: ignore[valid-type] + preisstaffeln: conlist(RegionalePreisstaffel, min_length=1) # type: ignore[valid-type] # optional attributes #: Gibt an, nach welcher Menge die vorgenannte Einschränkung erfolgt (z.B. Jahresstromverbrauch in kWh) diff --git a/src/bo4e/com/standorteigenschaftengas.py b/src/bo4e/com/standorteigenschaftengas.py index 56f71760d..5fe911040 100644 --- a/src/bo4e/com/standorteigenschaftengas.py +++ b/src/bo4e/com/standorteigenschaftengas.py @@ -26,5 +26,5 @@ class StandorteigenschaftenGas(COM): """ # required attributes - netzkontonummern: conlist(str, min_items=1, max_items=2) # type: ignore[valid-type] #: Netzkontonummern der Gasnetze + netzkontonummern: conlist(str, min_length=1, max_length=2) # type: ignore[valid-type] #: Netzkontonummern der Gasnetze marktgebiete: List[MarktgebietInfo] #: Die Informationen zu Marktgebieten in dem Netz. diff --git a/src/bo4e/com/tagesvektor.py b/src/bo4e/com/tagesvektor.py index 1d13459a2..54d68b689 100644 --- a/src/bo4e/com/tagesvektor.py +++ b/src/bo4e/com/tagesvektor.py @@ -33,7 +33,7 @@ class Tagesvektor(COM): Der Zeitpunkt sollte eindeutig sein, d.h. sowohl Datum+Uhrzeit als auch den UTC-Offset spezifizieren. """ # for the validator see also https://github.com/Hochfrequenz/BO4E-python/issues/262 - werte: conlist(Zeitreihenwertkompakt, min_items=1) # type: ignore[valid-type] + werte: conlist(Zeitreihenwertkompakt, min_length=1) # type: ignore[valid-type] """ Die Werte am angegebenen Tag; In Kombination aus Zeitintervall und Tag lassen sich die Zeiten der Werte eindeutig konstruieren. diff --git a/src/bo4e/com/tarifpreisposition.py b/src/bo4e/com/tarifpreisposition.py index 015c0850e..4fac1da9c 100644 --- a/src/bo4e/com/tarifpreisposition.py +++ b/src/bo4e/com/tarifpreisposition.py @@ -37,7 +37,7 @@ class Tarifpreisposition(COM): #: Größe, auf die sich die Einheit bezieht, beispielsweise kWh, Jahr bezugseinheit: Mengeneinheit #: Hier sind die Staffeln mit ihren Preisenangaben definiert - preisstaffeln: conlist(Preisstaffel, min_items=1) # type: ignore[valid-type] + preisstaffeln: conlist(Preisstaffel, min_length=1) # type: ignore[valid-type] # optional attributes #: Gibt an, nach welcher Menge die vorgenannte Einschränkung erfolgt (z.B. Jahresstromverbrauch in kWh) diff --git a/src/bo4e/com/tarifpreispositionproort.py b/src/bo4e/com/tarifpreispositionproort.py index 677a3efc4..403861861 100644 --- a/src/bo4e/com/tarifpreispositionproort.py +++ b/src/bo4e/com/tarifpreispositionproort.py @@ -26,11 +26,11 @@ class TarifpreispositionProOrt(COM): # required attributes #: Postleitzahl des Ortes für den der Preis gilt - postleitzahl: constr(strict=True, regex=r"^\d{5}$") # type: ignore[valid-type] + postleitzahl: constr(strict=True, pattern=r"^\d{5}$") # type: ignore[valid-type] #: Ort für den der Preis gilt ort: str #: ene't-Netznummer des Netzes in dem der Preis gilt netznr: str # Hier sind die Staffeln mit ihren Preisenangaben definiert - preisstaffeln: conlist(TarifpreisstaffelProOrt, min_items=1) # type: ignore[valid-type] + preisstaffeln: conlist(TarifpreisstaffelProOrt, min_length=1) # type: ignore[valid-type] # there are no optional attributes diff --git a/src/bo4e/com/verbrauch.py b/src/bo4e/com/verbrauch.py index f03a429e2..d0bd600bd 100644 --- a/src/bo4e/com/verbrauch.py +++ b/src/bo4e/com/verbrauch.py @@ -3,11 +3,12 @@ """ from datetime import datetime from decimal import Decimal -from typing import Any, Dict, Optional +from typing import Optional # pylint: disable=too-few-public-methods # pylint: disable=no-name-in-module -from pydantic import constr, validator +from pydantic import constr, field_validator +from pydantic_core.core_schema import ValidationInfo from bo4e.com.com import COM from bo4e.enum.mengeneinheit import Mengeneinheit @@ -32,7 +33,7 @@ class Verbrauch(COM): #: Gibt an, ob es sich um eine PROGNOSE oder eine MESSUNG handelt wertermittlungsverfahren: Wertermittlungsverfahren #: Die OBIS-Kennzahl für den Wert, die festlegt, welche Größe mit dem Stand gemeldet wird, z.B. '1-0: - obis_kennzahl: constr(strict=True, regex=OBIS_PATTERN) # type: ignore[valid-type] # type: ignore[valid-type] + obis_kennzahl: constr(strict=True, pattern=OBIS_PATTERN) # type: ignore[valid-type] # type: ignore[valid-type] #: Gibt den absoluten Wert der Menge an wert: Decimal #: Gibt die Einheit zum jeweiligen Wert an @@ -43,12 +44,12 @@ class Verbrauch(COM): startdatum: Optional[datetime] = None #: Exklusives Ende des Zeitraumes, für den der Verbrauch angegeben wird enddatum: Optional[datetime] = None - _bis_check = validator("enddatum", always=True, allow_reuse=True)(check_bis_is_later_than_von) + _bis_check = field_validator("enddatum")(check_bis_is_later_than_von) @staticmethod - def _get_inclusive_start(values: Dict[str, Any]) -> Optional[datetime]: + def _get_inclusive_start(values: ValidationInfo) -> Optional[datetime]: """a method for easier usage of the check_bis_is_later_than_von validator""" - return values["startdatum"] + return values.data["startdatum"] # type:ignore[attr-defined] # def _get_exclusive_end(self) -> Optional[datetime]: # """a method for easier usage of the check_bis_is_later_than_von validator""" diff --git a/src/bo4e/com/zaehlwerk.py b/src/bo4e/com/zaehlwerk.py index 6b3e70b3c..62ae2cdba 100644 --- a/src/bo4e/com/zaehlwerk.py +++ b/src/bo4e/com/zaehlwerk.py @@ -34,7 +34,7 @@ class Zaehlwerk(COM): richtung: Energierichtung # Die Energierichtung, Einspeisung oder Ausspeisung. obis_kennzahl: constr( # type: ignore[valid-type] strict=True, - regex=r"(?:(1)-((?:[0-5]?[0-9])|(?:6[0-5])):((?:[1-8]|99))\.((?:6|8|9|29))\.([0-9]{1,2}))|" + pattern=r"(?:(1)-((?:[0-5]?[0-9])|(?:6[0-5])):((?:[1-8]|99))\.((?:6|8|9|29))\.([0-9]{1,2}))|" r"(?:(7)-((?:[0-5]?[0-9])|(?:6[0-5])):(.{1,2})\.(.{1,2})\.([0-9]{1,2}))", ) # Die OBIS-Kennzahl für das Zählwerk, die festlegt, welche auf die gemessene Größe mit dem Stand gemeldet wird. # Nur Zählwerkstände mit dieser OBIS-Kennzahl werden an diesem Zählwerk registriert. diff --git a/src/bo4e/com/zeitraum.py b/src/bo4e/com/zeitraum.py index d6b2f81da..c7679d886 100644 --- a/src/bo4e/com/zeitraum.py +++ b/src/bo4e/com/zeitraum.py @@ -5,9 +5,10 @@ from datetime import datetime from decimal import Decimal -from typing import Any, Dict, Optional +from typing import Optional -from pydantic import validator +from pydantic import model_validator +from pydantic_core.core_schema import ValidationInfo from bo4e.com.com import COM from bo4e.enum.zeiteinheit import Zeiteinheit @@ -41,31 +42,49 @@ class Zeitraum(COM): endzeitpunkt: Optional[datetime] = None # pylint: disable=unused-argument, no-self-argument - @validator("endzeitpunkt", always=True) - def time_range_possibilities(cls, endzeitpunkt: Optional[datetime], values: Dict[str, Any]) -> Optional[datetime]: + @model_validator(mode="before") + def time_range_possibilities(cls, model: dict, validation_info: ValidationInfo) -> dict: # type:ignore[type-arg] """ An address is valid if it contains a postfach XOR (a strasse AND hausnummer). This functions checks for these conditions of a valid address. """ - + values = model + # todo: rewrite this to be more readable; just migrated to pydantic v2 without thinking too much about it + # https://github.com/bo4e/BO4E-python/issues/480 + endzeitpunkt = values.get("endzeitpunkt") if ( - values["einheit"] - and values["dauer"] - and not (values["startdatum"] or values["enddatum"] or values["startzeitpunkt"] or endzeitpunkt) + ("einheit" in values and values["einheit"]) + and ("dauer" in values and values["dauer"]) + and not ( + ("startdatum" in values and values["startdatum"]) + or ("enddatum" in values and values["enddatum"]) + or ("startzeitpunkt" in values and values["startzeitpunkt"]) + or endzeitpunkt + ) ): - return endzeitpunkt + return model if ( - values["startdatum"] - and values["enddatum"] - and not (values["einheit"] or values["dauer"] or values["startzeitpunkt"] or endzeitpunkt) + ("startdatum" in values and values["startdatum"]) + and ("enddatum" in values and values["enddatum"]) + and not ( + ("einheit" in values and values["einheit"]) + or ("dauer" in values and values["dauer"]) + or ("startzeitpunkt" in values and values["startzeitpunkt"]) + or endzeitpunkt + ) ): - return endzeitpunkt + return model if ( - values["startzeitpunkt"] - and endzeitpunkt - and not (values["einheit"] or values["dauer"] or values["startdatum"] or values["enddatum"]) + ("startzeitpunkt" in values and values["startzeitpunkt"]) + and ("endzeitpunkt" in values and model["endzeitpunkt"]) + and not ( + ("einheit" in values and values["einheit"]) + or ("dauer" in values and values["dauer"]) + or ("startdatum" in values and values["startdatum"]) + or ("enddatum" in values and values["enddatum"]) + ) ): - return endzeitpunkt + return model raise ValueError( """ diff --git a/src/bo4e/com/zeitreihenwert.py b/src/bo4e/com/zeitreihenwert.py index 958db93c2..1454eca24 100644 --- a/src/bo4e/com/zeitreihenwert.py +++ b/src/bo4e/com/zeitreihenwert.py @@ -3,9 +3,9 @@ and corresponding marshmallow schema for de-/serialization """ from datetime import datetime -from typing import Any, Dict -from pydantic import validator +from pydantic import field_validator +from pydantic_core.core_schema import ValidationInfo from bo4e.com.zeitreihenwertkompakt import Zeitreihenwertkompakt from bo4e.validators import check_bis_is_later_than_von @@ -29,12 +29,12 @@ class Zeitreihenwert(Zeitreihenwertkompakt): # required attributes datum_uhrzeit_von: datetime #: Datum Uhrzeit mit Auflösung Sekunden an dem das Messintervall begonnen wurde (inklusiv) datum_uhrzeit_bis: datetime #: Datum Uhrzeit mit Auflösung Sekunden an dem das Messintervall endet (exklusiv) - _bis_check = validator("datum_uhrzeit_bis", allow_reuse=True)(check_bis_is_later_than_von) + _bis_check = field_validator("datum_uhrzeit_bis")(check_bis_is_later_than_von) @staticmethod - def _get_inclusive_start(values: Dict[str, Any]) -> datetime: + def _get_inclusive_start(values: ValidationInfo) -> datetime: """return the inclusive start (used in the validator)""" - return values["datum_uhrzeit_von"] + return values.data["datum_uhrzeit_von"] # type:ignore[attr-defined] # def _get_exclusive_end(self) -> datetime: # """return the exclusive end (used in the validator)""" diff --git a/src/bo4e/utils/__init__.py b/src/bo4e/utils/__init__.py new file mode 100644 index 000000000..ed85b6b31 --- /dev/null +++ b/src/bo4e/utils/__init__.py @@ -0,0 +1,18 @@ +""" +utils necessary for reflection/inspection and documentation runs +""" + +from pydantic._internal._fields import PydanticGeneralMetadata +from pydantic.fields import FieldInfo + + +def is_constrained_str(model_field: FieldInfo) -> bool: + """ + returns True if the given model_field is a constrained string + """ + for metad in model_field.metadata: + if isinstance(metad, PydanticGeneralMetadata): + if hasattr(metad, "pattern"): + return True + return False + # return isinstance(model_field.outer_type_, type) and issubclass(model_field.outer_type_, str) diff --git a/src/bo4e/validators.py b/src/bo4e/validators.py index 2569c4f9e..a7573de0f 100644 --- a/src/bo4e/validators.py +++ b/src/bo4e/validators.py @@ -3,7 +3,9 @@ """ import re from datetime import datetime -from typing import Any, Dict, Optional, Protocol +from typing import Optional, Protocol + +from pydantic_core.core_schema import ValidationInfo from bo4e.enum.aufabschlagstyp import AufAbschlagstyp @@ -11,11 +13,12 @@ from bo4e.enum.waehrungseinheit import Waehrungseinheit -def einheit_only_for_abschlagstyp_absolut(cls, value: Waehrungseinheit, values: Dict[str, Any]) -> Waehrungseinheit: # type: ignore[no-untyped-def] +def einheit_only_for_abschlagstyp_absolut(cls, value: Waehrungseinheit, validation_info: ValidationInfo) -> Waehrungseinheit: # type: ignore[no-untyped-def] """ Check that einheit is only there if abschlagstyp is absolut. Currently, (2021-12-15) only used in COM AufAbschlag. """ + values = validation_info.data # type:ignore[attr-defined] if value and (not values["auf_abschlagstyp"] or (values["auf_abschlagstyp"] != AufAbschlagstyp.ABSOLUT)): raise ValueError("Only state einheit if auf_abschlagstyp is absolute.") return value @@ -28,7 +31,7 @@ class _VonBisType(Protocol): """ @staticmethod - def _get_inclusive_start(values: Dict[str, Any]) -> Optional[datetime]: + def _get_inclusive_start(values: ValidationInfo) -> Optional[datetime]: """ should return the inclusive start of the timeslice """ @@ -39,7 +42,7 @@ def _get_inclusive_start(values: Dict[str, Any]) -> Optional[datetime]: # """ -def check_bis_is_later_than_von(cls, value: datetime, values: Dict[str, Any]): # type: ignore[no-untyped-def] +def check_bis_is_later_than_von(cls, value: datetime, values: ValidationInfo): # type: ignore[no-untyped-def] """ assert that 'bis' is later than 'von' """ @@ -60,7 +63,7 @@ def check_bis_is_later_than_von(cls, value: datetime, values: Dict[str, Any]): # pylint: disable=unused-argument -def validate_marktlokations_id(cls, marktlokations_id: str, values: Dict[str, Any]) -> str: # type: ignore[no-untyped-def] +def validate_marktlokations_id(cls, marktlokations_id: str, values: ValidationInfo) -> str: # type: ignore[no-untyped-def] """ A validator for marktlokations IDs """ diff --git a/tests/serialization_helper.py b/tests/serialization_helper.py index fe2a1c6ef..1e77422c4 100644 --- a/tests/serialization_helper.py +++ b/tests/serialization_helper.py @@ -15,9 +15,9 @@ def assert_serialization_roundtrip(serializable_object: T, expected_json_dict: O then deserializes the dictionary again and asserts the equality with the original serializable_object :returns the deserialized_object """ - json_string = serializable_object.json(by_alias=True, ensure_ascii=False) # type: ignore[attr-defined] + json_string = serializable_object.model_dump_json(by_alias=True) # type: ignore[attr-defined] assert json_string is not None - actual_json_dict = serializable_object.dict(by_alias=True) # type: ignore[attr-defined] + actual_json_dict = serializable_object.model_dump(by_alias=True) # type: ignore[attr-defined] assert actual_json_dict is not None # TODO: serializable_object.dict() if expected_json_dict is not None: @@ -27,7 +27,7 @@ def assert_serialization_roundtrip(serializable_object: T, expected_json_dict: O ) # this (diff(...)) contains the difference between two dicts -> just for easier debugging - deserialized_object = type(serializable_object).parse_raw(json_string) # type: ignore[attr-defined] + deserialized_object = type(serializable_object).model_validate_json(json_string) # type: ignore[attr-defined] assert isinstance(deserialized_object, type(serializable_object)) assert deserialized_object == serializable_object return deserialized_object diff --git a/tests/test_adresse.py b/tests/test_adresse.py index 038cb1b6e..3765ee51e 100644 --- a/tests/test_adresse.py +++ b/tests/test_adresse.py @@ -32,14 +32,14 @@ def test_serialization_strasse(self) -> None: encoding="utf-8", ) - address_dict = address_test_data.json(by_alias=True, ensure_ascii=False) + address_dict = address_test_data.model_dump_json(by_alias=True) assert "Nördliche Münchner Straße" in address_dict assert "27A" in address_dict assert "82031" in address_dict assert "DE" in address_dict - deserialized_address = Adresse.parse_raw(address_dict) + deserialized_address = Adresse.model_validate_json(address_dict) assert isinstance(deserialized_address, Adresse) assert deserialized_address.strasse == "Nördliche Münchner Straße" @@ -61,13 +61,13 @@ def test_serialization_only_postfach(self) -> None: postfach=address_test_data["postfach"], ) - address_json = a.json(by_alias=True, ensure_ascii=False) + address_json = a.model_dump_json(by_alias=True) assert "10 64 38" in address_json assert "82031" in address_json assert "DE" in address_json - deserialized_address = Adresse.parse_raw(address_json) + deserialized_address = Adresse.model_validate_json(address_json) assert isinstance(deserialized_address, Adresse) assert deserialized_address.postfach == "10 64 38" @@ -86,12 +86,12 @@ def test_serialization_only_required_fields(self) -> None: ort=address_test_data["ort"], ) - address_json = a.json(by_alias=True, ensure_ascii=False) + address_json = a.model_dump_json(by_alias=True) assert "Grünwald" in address_json assert "82031" in address_json - deserialized_address = Adresse.parse_raw(address_json) + deserialized_address = Adresse.model_validate_json(address_json) assert isinstance(deserialized_address, Adresse) assert deserialized_address.ort == "Grünwald" @@ -104,8 +104,8 @@ def test_serialization_only_required_fields_landescode_AT(self) -> None: ) address_test_data.landescode = Landescode.AT # type: ignore[attr-defined] - address_json = address_test_data.json(by_alias=True, ensure_ascii=False) - deserialized_address = Adresse.parse_raw(address_json) + address_json = address_test_data.model_dump_json(by_alias=True) + deserialized_address = Adresse.model_validate_json(address_json) assert deserialized_address.landescode == Landescode.AT # type: ignore[attr-defined] @@ -116,7 +116,7 @@ def test_deserialization(self) -> None: "postleitzahl":"5020", "landescode":"AT"}""" - a: Adresse = Adresse.parse_raw(json_string) + a: Adresse = Adresse.model_validate_json(json_string) assert a.landescode is Landescode.AT # type: ignore[attr-defined] @pytest.mark.datafiles("./tests/test_data/test_data_adresse/test_data_adresse_missing_plz.json") @@ -199,9 +199,9 @@ def test_serialization_of_non_german_address(self) -> None: postleitzahl="6413", ort="Wildermieming", strasse="Gerhardhof", hausnummer="1", landescode=Landescode.AT # type: ignore[attr-defined] ) assert a.landescode == Landescode.AT # type: ignore[attr-defined] - serialized_address = a.json(by_alias=True, ensure_ascii=False) + serialized_address = a.model_dump_json(by_alias=True) assert '"AT"' in serialized_address - deserialized_address = Adresse.parse_raw(serialized_address) + deserialized_address = Adresse.model_validate_json(serialized_address) assert deserialized_address.landescode == Landescode.AT # type: ignore[attr-defined] @pytest.mark.parametrize( @@ -234,7 +234,7 @@ def test_serialization_with_all_possible_fields(self, address_json: str, adresse Test serialization with all required and optional attributes """ - serialized_address = adresse.json(by_alias=True, ensure_ascii=False) + serialized_address = adresse.model_dump_json(by_alias=True) assert "ISS spacestation" in serialized_address assert "12345" in serialized_address @@ -245,6 +245,6 @@ def test_serialization_with_all_possible_fields(self, address_json: str, adresse assert "you will find me" in serialized_address assert "Mitte" in serialized_address - deserialized_address = Adresse.parse_raw(address_json) + deserialized_address = Adresse.model_validate_json(address_json) assert deserialized_address == adresse diff --git a/tests/test_angebotsteil.py b/tests/test_angebotsteil.py index 203224582..61b2f57b7 100644 --- a/tests/test_angebotsteil.py +++ b/tests/test_angebotsteil.py @@ -188,7 +188,7 @@ def test_angebotsteil_positionen_required(self) -> None: _ = Angebotsteil(positionen=[]) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) def test_missing_required_attribute(self) -> None: with pytest.raises(ValidationError) as excinfo: diff --git a/tests/test_ansprechpartner.py b/tests/test_ansprechpartner.py index 4e568e8ab..b20de3946 100644 --- a/tests/test_ansprechpartner.py +++ b/tests/test_ansprechpartner.py @@ -45,12 +45,12 @@ def test_de_serialisation_minimal_attributes(self) -> None: assert ansprechpartner.versionstruktur == "2", "versionstruktur was not automatically set" assert ansprechpartner.bo_typ is BoTyp.ANSPRECHPARTNER, "boTyp was not automatically set" - json_string = ansprechpartner.json(by_alias=True, ensure_ascii=False) + json_string = ansprechpartner.model_dump_json(by_alias=True) assert "Müller-Schmidt" in json_string assert "Mühlenweg" in json_string assert '"FRAU"' in json_string - deserialized_ansprechpartner = Ansprechpartner.parse_raw(json_string) + deserialized_ansprechpartner = Ansprechpartner.model_validate_json(json_string) assert isinstance(deserialized_ansprechpartner, Ansprechpartner) assert isinstance(deserialized_ansprechpartner.geschaeftspartner, Geschaeftspartner) @@ -105,12 +105,12 @@ def test_de_serialisation_maximal_attributes(self) -> None: assert ansprechpartner.versionstruktur == "2", "versionstruktur was not automatically set" assert ansprechpartner.bo_typ is BoTyp.ANSPRECHPARTNER, "boTyp was not automatically set" - json_string = ansprechpartner.json(by_alias=True, ensure_ascii=False) + json_string = ansprechpartner.model_dump_json(by_alias=True) assert "Müller-Schmidt" in json_string assert "Mühlenweg" in json_string assert "PROF_DR" in json_string - deserialized_ansprechpartner = Ansprechpartner.parse_raw(json_string) + deserialized_ansprechpartner = Ansprechpartner.model_validate_json(json_string) assert isinstance(deserialized_ansprechpartner, Ansprechpartner) assert isinstance(deserialized_ansprechpartner.geschaeftspartner, Geschaeftspartner) diff --git a/tests/test_aufabschlagproort.py b/tests/test_aufabschlagproort.py index 754527f0f..8d9c4ecf3 100644 --- a/tests/test_aufabschlagproort.py +++ b/tests/test_aufabschlagproort.py @@ -65,4 +65,4 @@ def test_failing_validation_list_length_at_least_one(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_aufabschlagregional.py b/tests/test_aufabschlagregional.py index a76d800b5..dc5365cf6 100644 --- a/tests/test_aufabschlagregional.py +++ b/tests/test_aufabschlagregional.py @@ -236,4 +236,4 @@ def test_aufabschlagregional_betraege_required(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_ausschreibungsdetail.py b/tests/test_ausschreibungsdetail.py index 604b0c4f2..ef17da9e8 100644 --- a/tests/test_ausschreibungsdetail.py +++ b/tests/test_ausschreibungsdetail.py @@ -13,7 +13,6 @@ from bo4e.enum.zeiteinheit import Zeiteinheit from tests.serialization_helper import assert_serialization_roundtrip from tests.test_adresse import example_adresse -from tests.test_menge import example_menge from tests.test_zeitraum import example_zeitraum example_ausschreibungsdetail = Ausschreibungsdetail( diff --git a/tests/test_ausschreibungslos.py b/tests/test_ausschreibungslos.py index 49372dfa8..1d61fba58 100644 --- a/tests/test_ausschreibungslos.py +++ b/tests/test_ausschreibungslos.py @@ -89,7 +89,7 @@ def test_ausschreibungslos_lieferstellen_required(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) def test_missing_required_attribute(self) -> None: with pytest.raises(ValidationError) as excinfo: diff --git a/tests/test_bypassing_validation.py b/tests/test_bypassing_validation.py index c15b4b6b9..2a26ca942 100644 --- a/tests/test_bypassing_validation.py +++ b/tests/test_bypassing_validation.py @@ -1,5 +1,4 @@ import inspect -import json import pytest from pydantic import ValidationError @@ -37,14 +36,14 @@ def instantiate_with_constructor() -> Marktlokation: with pytest.raises(ValidationError) as validation_error: instantiate_with_constructor() error_msg = str(validation_error.value).replace("\n", " ").replace("\r", " ").replace(" ", " ") - assert "1 validation error for Marktlokation netzebene field required" in error_msg + assert "1 validation error for Marktlokation netzebene Field required" in error_msg # You're in a dilemma: # - either you cannot instantiate the BO, although you'd like to use BO4E # - or you have to guess values/enter dummy data which you cannot distinguish from real data later on. # The workaround is to use construct: def instantiate_with_construct() -> Marktlokation: - malo = Marktlokation.construct( # type:ignore[call-arg] # silence mypy complaints about the netzebene + malo = Marktlokation.model_construct( # type:ignore[call-arg] # silence mypy complaints about the netzebene marktlokations_id="51238696781", sparte=Sparte.GAS, lokationsadresse=Adresse( @@ -65,8 +64,8 @@ def instantiate_with_construct() -> Marktlokation: with pytest.raises(AttributeError): _ = marktlokation.netzebene # you can still serialize the invalid malo - malo_json_str = marktlokation.json() # works + malo_json_str = marktlokation.model_dump_json() # works assert malo_json_str is not None # but deserializing raises an error: with pytest.raises(ValidationError): - Marktlokation.parse_raw(malo_json_str) + Marktlokation.model_validate_json(malo_json_str) diff --git a/tests/test_doc_utils.py b/tests/test_doc_utils.py new file mode 100644 index 000000000..bf1b925b4 --- /dev/null +++ b/tests/test_doc_utils.py @@ -0,0 +1,8 @@ +from bo4e.bo.angebot import Angebot +from bo4e.utils import is_constrained_str + + +class TestDocUtils: + def test_is_constrained_str(self) -> None: + actual = is_constrained_str(Angebot.model_fields["angebotsnummer"]) + assert actual is True diff --git a/tests/test_energieherkunft.py b/tests/test_energieherkunft.py index af293e5dd..9961b1599 100644 --- a/tests/test_energieherkunft.py +++ b/tests/test_energieherkunft.py @@ -51,6 +51,4 @@ def test_energieherkunft_failing_validation(self, failing_percentage: float) -> _ = (Energieherkunft(erzeugungsart=Erzeugungsart.BIOMASSE, anteil_prozent=Decimal(failing_percentage)),) assert "1 validation error" in str(excinfo.value) - assert "ensure this value is less than 100" in str( - excinfo.value - ) or "ensure this value is greater than 0" in str(excinfo.value) + assert "should be less than 100" in str(excinfo.value) or "should be greater than 0" in str(excinfo.value) diff --git a/tests/test_energiemix.py b/tests/test_energiemix.py index 9a6c66334..95351b0ec 100644 --- a/tests/test_energiemix.py +++ b/tests/test_energiemix.py @@ -132,4 +132,4 @@ def test_energiemix_anteil_required(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_externe_referenz.py b/tests/test_externe_referenz.py index 16bec8fac..0d29118bb 100644 --- a/tests/test_externe_referenz.py +++ b/tests/test_externe_referenz.py @@ -10,11 +10,11 @@ class TestExterneReferenz: def test_serialization(self) -> None: er = ExterneReferenz(ex_ref_name="HOCHFREQUENZ_HFSAP_100", ex_ref_wert="12345") - er_json = er.json(by_alias=True, ensure_ascii=False) + er_json = er.model_dump_json(by_alias=True) assert "exRefName" in er_json - deserialized_er: ExterneReferenz = ExterneReferenz.parse_raw(er_json) + deserialized_er: ExterneReferenz = ExterneReferenz.model_validate_json(er_json) assert isinstance(deserialized_er, ExterneReferenz) assert deserialized_er == er @@ -37,9 +37,9 @@ def test_list_of_externe_referenz(self) -> None: ), ) - gp_json = gp.json(by_alias=True, ensure_ascii=False) + gp_json = gp.model_dump_json(by_alias=True) - deserialized_gp: Geschaeftspartner = Geschaeftspartner.parse_raw(gp_json) + deserialized_gp: Geschaeftspartner = Geschaeftspartner.model_validate_json(gp_json) assert len(deserialized_gp.externe_referenzen) == 2 # type: ignore[arg-type] assert deserialized_gp.externe_referenzen[0].ex_ref_name == "SAP GP Nummer" # type: ignore[index] @@ -58,9 +58,9 @@ def test_geschaeftspartner_with_no_externe_referenz(self) -> None: ), ) - gp_json = gp.json(by_alias=True, ensure_ascii=False) + gp_json = gp.model_dump_json(by_alias=True) - deserialized_gp: Geschaeftspartner = Geschaeftspartner.parse_raw(gp_json) + deserialized_gp: Geschaeftspartner = Geschaeftspartner.model_validate_json(gp_json) assert deserialized_gp.externe_referenzen == [] @@ -69,7 +69,7 @@ def test_extension_data(self) -> None: tests the behaviour of the json extension data (`extra="allow"`) """ er = ExterneReferenz(ex_ref_name="foo.bar", ex_ref_wert="12345") - er_json: Dict[str, Any] = er.dict() + er_json: Dict[str, Any] = er.model_dump() er_json["additional_key"] = "additional_value" deserialized_er: ExterneReferenz = ExterneReferenz.parse_obj(er_json) assert isinstance(deserialized_er, ExterneReferenz) diff --git a/tests/test_geokoordinaten.py b/tests/test_geokoordinaten.py index 1b5a9454c..fe4ad1e9f 100644 --- a/tests/test_geokoordinaten.py +++ b/tests/test_geokoordinaten.py @@ -13,12 +13,12 @@ def test_serialization(self) -> None: laengengrad=Decimal(13.404866218566895), ) - json_string = geo.json(by_alias=True, ensure_ascii=False) + json_string = geo.model_dump_json(by_alias=True) assert "breitengrad" in json_string assert str(geo.breitengrad) in json_string - deserialized_geo: Geokoordinaten = Geokoordinaten.parse_raw(json_string) + deserialized_geo: Geokoordinaten = Geokoordinaten.model_validate_json(json_string) assert isinstance(deserialized_geo.breitengrad, Decimal) assert isinstance(deserialized_geo.laengengrad, Decimal) diff --git a/tests/test_geraeteeigenschaften.py b/tests/test_geraeteeigenschaften.py index 63184d7e4..42c62553e 100644 --- a/tests/test_geraeteeigenschaften.py +++ b/tests/test_geraeteeigenschaften.py @@ -49,4 +49,4 @@ def test_failing_validation(self, not_a_geraetetyp: Any) -> None: _ = Geraeteeigenschaften(geraetemerkmal=Geraetemerkmal.GAS_G1000, geraetetyp=not_a_geraetetyp) assert "1 validation error" in str(excinfo.value) - assert "value is not a valid enumeration member" in str(excinfo.value) + assert "string_type" in str(excinfo.value) or "type=enum" in str(excinfo.value) diff --git a/tests/test_geschaeftsobjekt.py b/tests/test_geschaeftsobjekt.py index 1b2f0f18f..3c8c39509 100644 --- a/tests/test_geschaeftsobjekt.py +++ b/tests/test_geschaeftsobjekt.py @@ -38,11 +38,11 @@ def test_serialisation( ) assert isinstance(go, Geschaeftsobjekt) - go_json = go.json(by_alias=True, ensure_ascii=False) + go_json = go.model_dump_json(by_alias=True) assert str(versionstruktur) in go_json - go_deserialized = Geschaeftsobjekt.parse_raw(go_json) + go_deserialized = Geschaeftsobjekt.model_validate_json(go_json) assert go_deserialized.bo_typ is bo_typ assert go_deserialized.versionstruktur == versionstruktur @@ -60,5 +60,5 @@ def test_no_list_in_externen_referenzen(self) -> None: bo_typ=BoTyp.ENERGIEMENGE, externe_referenzen=ExterneReferenz(ex_ref_name="Schufa-ID", ex_ref_wert="aksdlakoeuhn"), # type: ignore[arg-type] ) - assert "1 validation error" in str(excinfo.value) - assert "value is not a valid list" in str(excinfo.value) + assert "2 validation error" in str(excinfo.value) + assert "type=model_type" in str(excinfo.value) diff --git a/tests/test_geschaeftspartner.py b/tests/test_geschaeftspartner.py index c0c4a5cf1..2bec62e69 100644 --- a/tests/test_geschaeftspartner.py +++ b/tests/test_geschaeftspartner.py @@ -44,11 +44,11 @@ def test_serializable(self, datafiles: Path) -> None: # test default value for bo_typ in Geschaeftspartner assert gp.bo_typ == BoTyp.GESCHAEFTSPARTNER - gp_json = gp.json(by_alias=True, ensure_ascii=False) + gp_json = gp.model_dump_json(by_alias=True) assert "Helga" in gp_json - gp_deserialized = Geschaeftspartner.parse_raw(gp_json) + gp_deserialized = Geschaeftspartner.model_validate_json(gp_json) assert gp_deserialized.bo_typ == gp.bo_typ assert type(gp_deserialized.partneradresse) == Adresse @@ -77,8 +77,8 @@ def test_optional_attribute_partneradresse(self) -> None: geschaeftspartnerrolle=[Geschaeftspartnerrolle.DIENSTLEISTER], ) - gp_json = gp.json(by_alias=True, ensure_ascii=False) - gp_deserialized = Geschaeftspartner.parse_raw(gp_json) + gp_json = gp.model_dump_json(by_alias=True) + gp_deserialized = Geschaeftspartner.model_validate_json(gp_json) assert gp_deserialized.partneradresse is None @@ -115,7 +115,7 @@ def test_list_validation_of_geschaeftspartnerrolle(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "value is not a valid list" in str(excinfo.value) + assert "type=list_type" in str(excinfo.value) def test_serialization_of_non_german_address(self) -> None: """ @@ -136,6 +136,6 @@ def test_serialization_of_non_german_address(self) -> None: postleitzahl="1014", ort="Wien 1", strasse="Ballhausplatz", hausnummer="2", landescode=Landescode.AT # type: ignore[attr-defined] ), ) - gp_json = gp.json(by_alias=True, ensure_ascii=False) - gp_deserialized = Geschaeftspartner.parse_raw(gp_json) + gp_json = gp.model_dump_json(by_alias=True) + gp_deserialized = Geschaeftspartner.model_validate_json(gp_json) assert gp_deserialized.partneradresse.landescode == Landescode.AT # type: ignore[attr-defined, union-attr] diff --git a/tests/test_katasteradresse.py b/tests/test_katasteradresse.py index 08d75389e..b80b3494d 100644 --- a/tests/test_katasteradresse.py +++ b/tests/test_katasteradresse.py @@ -5,10 +5,10 @@ class TestKatasteradresse: def test_serialization(self) -> None: ka = Katasteradresse(gemarkung_flur="hello", flurstueck="world") - json_string = ka.json(by_alias=True, ensure_ascii=False) + json_string = ka.model_dump_json(by_alias=True) assert "gemarkungFlur" in json_string, "No camel case serialization" - deserialized_ka: Katasteradresse = Katasteradresse.parse_raw(json_string) + deserialized_ka: Katasteradresse = Katasteradresse.model_validate_json(json_string) assert ka.gemarkung_flur == deserialized_ka.gemarkung_flur diff --git a/tests/test_marktgebietinfo.py b/tests/test_marktgebietinfo.py index 965c07a96..263c013a4 100644 --- a/tests/test_marktgebietinfo.py +++ b/tests/test_marktgebietinfo.py @@ -5,10 +5,10 @@ class TestMarktgebietinfo: def test_serialization(self) -> None: mgi = MarktgebietInfo(marktgebiet="Gaspool", marktgebietcode="37Z701133MH0000B") - json_string = mgi.json(by_alias=True, ensure_ascii=False) + json_string = mgi.model_dump_json(by_alias=True) assert "marktgebiet" in json_string, "No camel case serialization" - deserialized_mgi: MarktgebietInfo = MarktgebietInfo.parse_raw(json_string) + deserialized_mgi: MarktgebietInfo = MarktgebietInfo.model_validate_json(json_string) assert mgi == deserialized_mgi diff --git a/tests/test_marktlokation.py b/tests/test_marktlokation.py index 1db1c41b9..281cdf83c 100644 --- a/tests/test_marktlokation.py +++ b/tests/test_marktlokation.py @@ -35,12 +35,12 @@ def test_serialisation_only_required_attributes(self) -> None: assert malo.versionstruktur == "2", "versionstruktur was not automatically set" assert malo.bo_typ is BoTyp.MARKTLOKATION, "boTyp was not automatically set" - json_string = malo.json(by_alias=True, ensure_ascii=False) + json_string = malo.model_dump_json(by_alias=True) assert "boTyp" in json_string, "No camel case serialization" assert "marktlokationsId" in json_string, "No camel case serialization" - deserialized_malo: Marktlokation = Marktlokation.parse_raw(json_string) + deserialized_malo: Marktlokation = Marktlokation.model_validate_json(json_string) # check that `deserialized_malo.marktlokations_id` and `malo.marktlokations_id` have the same value # but are **not** the same object. @@ -87,12 +87,12 @@ def test_serialization_required_and_optional_attributes(self) -> None: assert malo.versionstruktur == "2", "versionstruktur was not automatically set" assert malo.bo_typ == BoTyp.MARKTLOKATION, "boTyp was not automatically set" - json_string = malo.json(by_alias=True, ensure_ascii=False) + json_string = malo.model_dump_json(by_alias=True) assert "boTyp" in json_string, "No camel case serialization" assert "marktlokationsId" in json_string, "No camel case serialization" - deserialized_malo: Marktlokation = Marktlokation.parse_raw(json_string) + deserialized_malo: Marktlokation = Marktlokation.model_validate_json(json_string) assert deserialized_malo.marktlokations_id == malo.marktlokations_id assert deserialized_malo.marktlokations_id is not malo.marktlokations_id @@ -137,11 +137,11 @@ def test_missing_required_fields(self) -> None: }""" with pytest.raises(ValidationError) as excinfo: - Marktlokation.parse_raw(invalid_json_string) + Marktlokation.model_validate_json(invalid_json_string) assert "1 validation error" in str(excinfo.value) assert "marktlokationsId" in str(excinfo.value) - assert "field required" in str(excinfo.value) + assert "Field required" in str(excinfo.value) def test_address_validation(self) -> None: with pytest.raises(ValidationError) as excinfo: diff --git a/tests/test_marktteilnehmer.py b/tests/test_marktteilnehmer.py index 499778898..6ff7074ba 100644 --- a/tests/test_marktteilnehmer.py +++ b/tests/test_marktteilnehmer.py @@ -34,14 +34,14 @@ def test_serialization(self) -> None: assert mt.versionstruktur == "2", "versionstruktur was not automatically set" assert mt.bo_typ == BoTyp.MARKTTEILNEHMER, "boTyp was not automatically set" - json_string = mt.json(by_alias=True, ensure_ascii=False) + json_string = mt.model_dump_json(by_alias=True) json_dict = json.loads(json_string) # Test camelcase assert "boTyp" in json_dict assert "marktrolle" in json_dict - deserialized_mt: Marktteilnehmer = Marktteilnehmer.parse_raw(json_string) + deserialized_mt: Marktteilnehmer = Marktteilnehmer.model_validate_json(json_string) assert mt.marktrolle is deserialized_mt.marktrolle # Test snakecase diff --git a/tests/test_menge.py b/tests/test_menge.py index 3d2974bbe..15ab947d9 100644 --- a/tests/test_menge.py +++ b/tests/test_menge.py @@ -20,12 +20,12 @@ def test_menge(self) -> None: Test de-/serialisation of Menge (only has required attributes). """ - json_string = example_menge.json(by_alias=True, ensure_ascii=False) + json_string = example_menge.model_dump_json(by_alias=True) assert "3.41" in json_string assert "MWH" in json_string - menge_deserialized = Menge.parse_raw(json_string) + menge_deserialized = Menge.model_validate_json(json_string) assert isinstance(menge_deserialized.wert, Decimal) assert menge_deserialized.wert == Decimal(3.41) diff --git a/tests/test_messlokation.py b/tests/test_messlokation.py index 63f5e2985..add745203 100644 --- a/tests/test_messlokation.py +++ b/tests/test_messlokation.py @@ -39,13 +39,13 @@ def test_serialisation_only_required_attributes(self) -> None: assert melo.versionstruktur == "2", "versionstruktur was not automatically set" assert melo.bo_typ is BoTyp.MESSLOKATION, "boTyp was not automatically set" - json_string = melo.json(by_alias=True, ensure_ascii=False) + json_string = melo.model_dump_json(by_alias=True) json_dict = json.loads(json_string) assert "boTyp" in json_dict, "No camel case serialization" assert "messlokationsId" in json_dict, "No camel case serialization" - deserialized_melo: Messlokation = Messlokation.parse_raw(json_string) + deserialized_melo: Messlokation = Messlokation.model_validate_json(json_string) # check that `deserialized_malo.marktlokations_id` and `malo.marktlokations_id` have the same value # but are **not** the same object. @@ -108,13 +108,13 @@ def test_serialization_required_and_optional_attributes(self) -> None: assert melo.versionstruktur == "2", "versionstruktur was not automatically set" assert melo.bo_typ == BoTyp.MESSLOKATION, "boTyp was not automatically set" - json_string = melo.json(by_alias=True, ensure_ascii=False) + json_string = melo.model_dump_json(by_alias=True) json_dict = json.loads(json_string) assert "boTyp" in json_dict, "No camel case serialization" assert "messlokationsId" in json_dict, "No camel case serialization" - deserialized_melo: Messlokation = Messlokation.parse_raw(json_string) + deserialized_melo: Messlokation = Messlokation.model_validate_json(json_string) assert deserialized_melo.messlokations_id == melo.messlokations_id assert deserialized_melo.messlokations_id is not melo.messlokations_id @@ -155,7 +155,7 @@ def test_missing_required_fields(self) -> None: """ with pytest.raises(ValidationError) as excinfo: - Messlokation.parse_raw(invalid_json_string) + Messlokation.model_validate_json(invalid_json_string) assert "messlokationsId" in str(excinfo.value) @@ -224,7 +224,7 @@ def test_extension_data(self) -> None: messlokations_id="DE00056266802AO6G56M11SN51G21M24S", sparte=Sparte.STROM, ) - melo_json: Dict[str, Any] = melo.dict() + melo_json: Dict[str, Any] = melo.model_dump() melo_json["additional_key"] = "additional_value" deserialized_melo: Messlokation = Messlokation.parse_obj(melo_json) assert isinstance(deserialized_melo, Messlokation) diff --git a/tests/test_messlokationszuordnung.py b/tests/test_messlokationszuordnung.py index a2f1182d9..3c846be8f 100644 --- a/tests/test_messlokationszuordnung.py +++ b/tests/test_messlokationszuordnung.py @@ -16,12 +16,12 @@ def test_serialisation_only_required_attributes(self) -> None: arithmetik=ArithmetischeOperation.ADDITION, ) - json_string = mlz.json(by_alias=True, ensure_ascii=False) + json_string = mlz.model_dump_json(by_alias=True) assert messlokations_id in json_string assert "ADDITION" in json_string - mlz_deserialized = Messlokationszuordnung.parse_raw(json_string) + mlz_deserialized = Messlokationszuordnung.model_validate_json(json_string) assert mlz_deserialized.messlokations_id == messlokations_id assert mlz_deserialized.arithmetik == ArithmetischeOperation.ADDITION @@ -39,7 +39,7 @@ def test_serialisation_required_and_optional_attributes(self) -> None: gueltig_bis=datetime(year=2021, month=5, day=4), ) - mlz_json = mlz.json(by_alias=True, ensure_ascii=False) + mlz_json = mlz.model_dump_json(by_alias=True) # CamelCase keys are made because they will put into JSON strings # to send them to the frontend (= JavaScript land) @@ -50,7 +50,7 @@ def test_serialisation_required_and_optional_attributes(self) -> None: assert "gueltigSeit" in mlz_json assert "2021-01-13T00:00:00" in mlz_json - mlz_deserialized = Messlokationszuordnung.parse_raw(mlz_json) + mlz_deserialized = Messlokationszuordnung.model_validate_json(mlz_json) assert mlz_deserialized.messlokations_id == messlokations_id assert mlz_deserialized.arithmetik == ArithmetischeOperation.ADDITION diff --git a/tests/test_preis.py b/tests/test_preis.py index 6645c3a9a..68b2e918a 100644 --- a/tests/test_preis.py +++ b/tests/test_preis.py @@ -18,13 +18,13 @@ def test_preis_only_required(self) -> None: """ preis = example_preis - json_string = preis.json(by_alias=True, ensure_ascii=False) + json_string = preis.model_dump_json(by_alias=True) assert "KWH" in json_string assert "EUR" in json_string assert "null" in json_string - preis_deserialized = Preis.parse_raw(json_string) + preis_deserialized = Preis.model_validate_json(json_string) assert isinstance(preis_deserialized.wert, Decimal) assert isinstance(preis_deserialized.einheit, Waehrungseinheit) @@ -38,7 +38,7 @@ def test_wrong_datatype(self) -> None: assert "1 validation error" in str(excinfo.value) assert "wert" in str(excinfo.value) - assert "value is not a valid decimal" in str(excinfo.value) + assert "type=decimal_parsing" in str(excinfo.value) def test_missing_required_attribute(self) -> None: with pytest.raises(ValidationError) as excinfo: @@ -54,10 +54,10 @@ def test_optional_attribute(self) -> None: status=Preisstatus.ENDGUELTIG, ) - json_string = preis.json(by_alias=True, ensure_ascii=False) + json_string = preis.model_dump_json(by_alias=True) assert "ENDGUELTIG" in json_string - preis_deserialized = Preis.parse_raw(json_string) + preis_deserialized = Preis.model_validate_json(json_string) assert isinstance(preis_deserialized.status, Preisstatus) diff --git a/tests/test_region.py b/tests/test_region.py index 2e2d0a6a6..1be37cee0 100644 --- a/tests/test_region.py +++ b/tests/test_region.py @@ -41,4 +41,4 @@ def test_region_positiv_liste_required_and_negativ_liste_not_required(self) -> N ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_regionalegueltigkeit.py b/tests/test_regionalegueltigkeit.py index f9bd9541a..d3c8bb07e 100644 --- a/tests/test_regionalegueltigkeit.py +++ b/tests/test_regionalegueltigkeit.py @@ -59,4 +59,4 @@ def test_regionalegueltigkeit_kriteriumswerte_required(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_regionaltarif.py b/tests/test_regionaltarif.py index 570ef7d54..bcc311a35 100644 --- a/tests/test_regionaltarif.py +++ b/tests/test_regionaltarif.py @@ -99,4 +99,4 @@ def test_failing_validation_list_length_at_least_one(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_standorteigenschaftengas.py b/tests/test_standorteigenschaftengas.py index aa9f9d29f..d018d61dc 100644 --- a/tests/test_standorteigenschaftengas.py +++ b/tests/test_standorteigenschaftengas.py @@ -42,11 +42,11 @@ def test_standorteigenschaftengas_missing_required_attributes(self) -> None: [ pytest.param( [], - "ensure this value has at least 1 item", + "too_short", ), pytest.param( ["1", "2", "3"], - "ensure this value has at most 2 items", + "should have at most 2 items", ), ], ) diff --git a/tests/test_tagesvektor.py b/tests/test_tagesvektor.py index c37a09853..95a4feaf4 100644 --- a/tests/test_tagesvektor.py +++ b/tests/test_tagesvektor.py @@ -57,6 +57,6 @@ def test_list_not_long_enough_attribute(self) -> None: _ = Tagesvektor(tag=datetime(2021, 12, 15, 5, 0, tzinfo=timezone.utc), werte=[]) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) # add tests for issues 261 and 262 here diff --git a/tests/test_tarifpreis.py b/tests/test_tarifpreis.py index 5c62be65b..04cf5861e 100644 --- a/tests/test_tarifpreis.py +++ b/tests/test_tarifpreis.py @@ -24,12 +24,12 @@ def test_tarifpreis_only_required(self) -> None: """ tarifpreis = example_tarifpreis - json_string = tarifpreis.json(by_alias=True, ensure_ascii=False) + json_string = tarifpreis.model_dump_json(by_alias=True) assert "ARBEITSPREIS_HT" in json_string assert "null" in json_string - tarifpreis_deserialized = Tarifpreis.parse_raw(json_string) + tarifpreis_deserialized = Tarifpreis.model_validate_json(json_string) assert isinstance(tarifpreis_deserialized.wert, Decimal) assert isinstance(tarifpreis_deserialized.einheit, Waehrungseinheit) @@ -49,7 +49,7 @@ def test_wrong_datatype(self) -> None: assert "1 validation error" in str(excinfo.value) assert "wert" in str(excinfo.value) - assert "value is not a valid decimal" in str(excinfo.value) + assert "should be a valid decimal" in str(excinfo.value) def test_missing_required_attribute(self) -> None: with pytest.raises(ValidationError) as excinfo: @@ -72,11 +72,11 @@ def test_optional_attribute(self) -> None: beschreibung="Das ist ein HT Arbeitspreis", ) - json_string = tarifpreis.json(by_alias=True, ensure_ascii=False) + json_string = tarifpreis.model_dump_json(by_alias=True) assert "Das ist ein HT Arbeitspreis" in json_string - tarifpreis_deserialized = Tarifpreis.parse_raw(json_string) + tarifpreis_deserialized = Tarifpreis.model_validate_json(json_string) assert isinstance(tarifpreis_deserialized.beschreibung, str) assert tarifpreis_deserialized.beschreibung == "Das ist ein HT Arbeitspreis" diff --git a/tests/test_tarifpreisblatt.py b/tests/test_tarifpreisblatt.py index ee792e73a..3928ae2ab 100644 --- a/tests/test_tarifpreisblatt.py +++ b/tests/test_tarifpreisblatt.py @@ -14,10 +14,8 @@ from tests.serialization_helper import assert_serialization_roundtrip from tests.test_aufabschlag import example_aufabschlag from tests.test_energiemix import example_energiemix -from tests.test_geraeteeigenschaften import example_geraeteeigenschaften from tests.test_marktteilnehmer import example_marktteilnehmer from tests.test_preisgarantie import example_preisgarantie -from tests.test_preisposition import example_preisposition from tests.test_tarifpreisposition import example_tarifpreisposition from tests.test_vertragskonditionen import example_vertragskonditionen from tests.test_zeitraum import example_zeitraum diff --git a/tests/test_tarifpreisposition.py b/tests/test_tarifpreisposition.py index fb352beed..80a09765f 100644 --- a/tests/test_tarifpreisposition.py +++ b/tests/test_tarifpreisposition.py @@ -103,4 +103,4 @@ def test_tarifpreisposition_betraege_required(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_unterschrift.py b/tests/test_unterschrift.py index adf3d3eb8..6dc3e6fd6 100644 --- a/tests/test_unterschrift.py +++ b/tests/test_unterschrift.py @@ -13,11 +13,11 @@ def test_unterschrift_only_required_attributes(self) -> None: """ unterschrift = Unterschrift(name="Foo") - json_string = unterschrift.json(by_alias=True, ensure_ascii=False) + json_string = unterschrift.model_dump_json(by_alias=True) assert "Foo" in json_string - unterschrift_deserialized = Unterschrift.parse_raw(json_string) + unterschrift_deserialized = Unterschrift.model_validate_json(json_string) assert isinstance(unterschrift_deserialized.name, str) assert unterschrift_deserialized.name == "Foo" @@ -28,13 +28,13 @@ def test_unterschrift_required_and_optional_attributes(self) -> None: """ unterschrift = Unterschrift(name="Foo", ort="Grünwald", datum=datetime(2019, 6, 7, tzinfo=timezone.utc)) - json_string = unterschrift.json(by_alias=True, ensure_ascii=False) + json_string = unterschrift.model_dump_json(by_alias=True) assert "Foo" in json_string assert "Grünwald" in json_string - assert "2019-06-07T00:00:00+00:00" in json_string + assert "2019-06-07T00:00:00Z" in json_string - unterschrift_deserialized = Unterschrift.parse_raw(json_string) + unterschrift_deserialized = Unterschrift.model_validate_json(json_string) assert isinstance(unterschrift_deserialized.name, str) assert unterschrift_deserialized.name == "Foo" diff --git a/tests/test_verbrauch.py b/tests/test_verbrauch.py index eaba0df3e..094244f7f 100644 --- a/tests/test_verbrauch.py +++ b/tests/test_verbrauch.py @@ -83,8 +83,8 @@ def test_failing_validation_obis(self, not_a_valid_obis: str) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "obisKennzahl" in str(excinfo.value) - assert "string does not match regex" in str(excinfo.value) + assert "obis_kennzahl" in str(excinfo.value) + assert "should match pattern" in str(excinfo.value) def test_failing_validation_end_later_than_start(self) -> None: with pytest.raises(ValidationError) as excinfo: diff --git a/tests/test_vertrag.py b/tests/test_vertrag.py index 7bc02e261..fa08d0232 100644 --- a/tests/test_vertrag.py +++ b/tests/test_vertrag.py @@ -172,20 +172,20 @@ def test_serialisation_only_required_attributes(self) -> None: """ vertrag = self.get_example_vertrag() - json_string = vertrag.json(by_alias=True, ensure_ascii=False) + json_string = vertrag.model_dump_json(by_alias=True) assert vertrag.bo_typ is BoTyp.VERTRAG, "boTyp was not automatically set" assert self._vertragsnummer in json_string assert "BILANZIERUNGSVERTRAG" in json_string assert "AKTIV" in json_string assert "STROM" in json_string - assert "2021-04-30T13:45:00+00:00" in json_string - assert "2021-06-05T16:30:00+00:00" in json_string + assert "2021-04-30T13:45:00Z" in json_string + assert "2021-06-05T16:30:00Z" in json_string assert "von Sinnen" in json_string assert "Preetz" in json_string - assert "2021-06-05T00:00:00+00:00" in json_string + assert "2021-06-05T00:00:00Z" in json_string - vertrag_deserialized = Vertrag.parse_raw(json_string) + vertrag_deserialized = Vertrag.model_validate_json(json_string) assert vertrag_deserialized.vertragsnummer == self._vertragsnummer assert vertrag_deserialized.vertragsart == self._vertragsart @@ -227,26 +227,26 @@ def test_serialisation_required_and_optional_attributes(self) -> None: unterzeichnervp2=[Unterschrift(name="Bar"), Unterschrift(name="Dr.No")], ) - json_string = vertrag.json(by_alias=True, ensure_ascii=False) + json_string = vertrag.model_dump_json(by_alias=True) assert vertrag.bo_typ is BoTyp.VERTRAG, "boTyp was not automatically set" assert self._vertragsnummer in json_string assert "BILANZIERUNGSVERTRAG" in json_string assert "AKTIV" in json_string assert "STROM" in json_string - assert "2021-04-30T13:45:00+00:00" in json_string - assert "2021-06-05T16:30:00+00:00" in json_string + assert "2021-04-30T13:45:00Z" in json_string + assert "2021-06-05T16:30:00Z" in json_string assert "von Sinnen" in json_string assert "Preetz" in json_string - assert "2021-06-05T00:00:00+00:00" in json_string - assert "2002-12-03T00:00:00+00:00" in json_string + assert "2021-06-05T00:00:00Z" in json_string + assert "2002-12-03T00:00:00Z" in json_string assert "Hello Vertrag" in json_string assert "Beschreibung" in json_string assert "Foo" in json_string assert "Bar" in json_string assert "Dr.No" in json_string - vertrag_deserialized = Vertrag.parse_raw(json_string) + vertrag_deserialized = Vertrag.model_validate_json(json_string) assert vertrag_deserialized.vertragsnummer == self._vertragsnummer assert vertrag_deserialized.vertragsart == self._vertragsart @@ -295,4 +295,4 @@ def test_serialization_fails_for_empty_vertragsteile(self) -> None: ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_vertragskonditionen.py b/tests/test_vertragskonditionen.py index ebfc4a0d4..801b5516d 100644 --- a/tests/test_vertragskonditionen.py +++ b/tests/test_vertragskonditionen.py @@ -25,16 +25,16 @@ def test_vertragskonditionen_with_optional_attributes(self) -> None: """ vertragskonditionen = example_vertragskonditionen - json_string = vertragskonditionen.json(by_alias=True, ensure_ascii=False) + json_string = vertragskonditionen.model_dump_json(by_alias=True) assert "Foobar" in json_string assert "3" in json_string - assert "2013-10-11T00:00:00+00:00" in json_string + assert "2013-10-11T00:00:00Z" in json_string assert "WOCHE" in json_string assert "TAG" in json_string assert "14" in json_string - vertragskonditionen_deserialized = Vertragskonditionen.parse_raw(json_string) + vertragskonditionen_deserialized = Vertragskonditionen.model_validate_json(json_string) assert isinstance(vertragskonditionen_deserialized.beschreibung, str) assert vertragskonditionen_deserialized.beschreibung == "Foobar" diff --git a/tests/test_vertragsteil.py b/tests/test_vertragsteil.py index 3194c06b5..d9e379764 100644 --- a/tests/test_vertragsteil.py +++ b/tests/test_vertragsteil.py @@ -19,12 +19,12 @@ def test_vertragsteil_only_required_attributes(self) -> None: vertragsteilende=datetime(2007, 11, 27, tzinfo=timezone.utc), ) - json_string = vertragsteil.json(by_alias=True, ensure_ascii=False) + json_string = vertragsteil.model_dump_json(by_alias=True) - assert "2001-03-15T00:00:00+00:00" in json_string - assert "2007-11-27T00:00:00+00:00" in json_string + assert "2001-03-15T00:00:00Z" in json_string + assert "2007-11-27T00:00:00Z" in json_string - vertragsteil_deserialized = Vertragsteil.parse_raw(json_string) + vertragsteil_deserialized = Vertragsteil.model_validate_json(json_string) assert isinstance(vertragsteil_deserialized.vertragsteilbeginn, datetime) assert vertragsteil_deserialized.vertragsteilbeginn == datetime(2001, 3, 15, tzinfo=timezone.utc) @@ -44,15 +44,15 @@ def test_vertragsteil_required_and_optional_attributes(self) -> None: maximale_abnahmemenge=Menge(wert=Decimal(0.111111), einheit=Mengeneinheit.KWH), ) - json_string = vertragsteil.json(by_alias=True, ensure_ascii=False) + json_string = vertragsteil.model_dump_json(by_alias=True) - assert "2001-03-15T00:00:00+00:00" in json_string - assert "2007-11-27T00:00:00+00:00" in json_string + assert "2001-03-15T00:00:00Z" in json_string + assert "2007-11-27T00:00:00Z" in json_string assert "Bar" in json_string assert "KWH" in json_string assert "0.111111" in json_string - vertragsteil_deserialized = Vertragsteil.parse_raw(json_string) + vertragsteil_deserialized = Vertragsteil.model_validate_json(json_string) assert isinstance(vertragsteil_deserialized.vertragsteilbeginn, datetime) assert vertragsteil_deserialized.vertragsteilbeginn == datetime(2001, 3, 15, tzinfo=timezone.utc) diff --git a/tests/test_zaehler.py b/tests/test_zaehler.py index 5f2e7c3b9..5460167c4 100644 --- a/tests/test_zaehler.py +++ b/tests/test_zaehler.py @@ -46,10 +46,10 @@ def test_de_serialisation(self) -> None: assert zaehler.bo_typ is BoTyp.ZAEHLER, "boTyp was not automatically set" assert zaehler.zaehlwerke[0].richtung == Energierichtung.EINSP assert zaehler.zaehlwerke[0].einheit == Mengeneinheit.KW - json_string = zaehler.json(by_alias=True, ensure_ascii=False) + json_string = zaehler.model_dump_json(by_alias=True) assert "richtung" in json_string, "Zaehlwerk->richtung was not serialized" assert "einheit" in json_string, "Zaehlwerk->einheit was not serialized" - deserialized_zaehler = Zaehler.parse_raw(json_string) + deserialized_zaehler = Zaehler.model_validate_json(json_string) assert deserialized_zaehler == zaehler def test_serialization_fails_for_invalid_obis(self) -> None: @@ -75,8 +75,8 @@ def test_serialization_fails_for_invalid_obis(self) -> None: tarifart=Tarifart.ZWEITARIF, ) assert "1 validation error" in str(excinfo.value) - assert "obisKennzahl" in str(excinfo.value) - assert "string does not match regex" in str(excinfo.value) + assert "obis_kennzahl" in str(excinfo.value) + assert "should match pattern" in str(excinfo.value) def test_serialization_fails_for_empty_zaehlwerke(self) -> None: """ @@ -92,4 +92,4 @@ def test_serialization_fails_for_empty_zaehlwerke(self) -> None: tarifart=Tarifart.ZWEITARIF, ) assert "1 validation error" in str(excinfo.value) - assert "ensure this value has at least 1 item" in str(excinfo.value) + assert "too_short" in str(excinfo.value) diff --git a/tests/test_zeitintervall.py b/tests/test_zeitintervall.py index 59ce69717..a936a2f2e 100644 --- a/tests/test_zeitintervall.py +++ b/tests/test_zeitintervall.py @@ -12,12 +12,12 @@ def test_zeitintervall_daten(self) -> None: """ zeitintervall = Zeitintervall(wert=2, zeiteinheit=Zeiteinheit.VIERTEL_STUNDE) - json_string = zeitintervall.json(by_alias=True, ensure_ascii=False) + json_string = zeitintervall.model_dump_json(by_alias=True) assert "2" in json_string assert "VIERTEL_STUNDE" in json_string - zeitintervall_deserialized = Zeitintervall.parse_raw(json_string) + zeitintervall_deserialized = Zeitintervall.model_validate_json(json_string) assert isinstance(zeitintervall_deserialized.wert, int) assert zeitintervall_deserialized.wert == 2 @@ -30,7 +30,7 @@ def test_wrong_datatype(self) -> None: assert "1 validation error" in str(excinfo.value) assert "wert" in str(excinfo.value) - assert "value is not a valid integer" in str(excinfo.value) + assert "should be a valid integer" in str(excinfo.value) def test_missing_required_attribute(self) -> None: with pytest.raises(ValidationError) as excinfo: diff --git a/tests/test_zeitraum.py b/tests/test_zeitraum.py index f1d819614..d35d4e8db 100644 --- a/tests/test_zeitraum.py +++ b/tests/test_zeitraum.py @@ -29,12 +29,12 @@ def test_zeitraum_dauer(self) -> None: """ zeitraum = Zeitraum(einheit=Zeiteinheit.TAG, dauer=Decimal(21)) - json_string = zeitraum.json(by_alias=True, ensure_ascii=False) + json_string = zeitraum.model_dump_json(by_alias=True) assert "21" in json_string assert "TAG" in json_string - zeitraum_deserialized = Zeitraum.parse_raw(json_string) + zeitraum_deserialized = Zeitraum.model_validate_json(json_string) assert isinstance(zeitraum_deserialized.einheit, Zeiteinheit) assert zeitraum_deserialized.einheit == Zeiteinheit.TAG @@ -49,12 +49,12 @@ def test_zeitraum_daten(self) -> None: startdatum=datetime(2013, 5, 1, tzinfo=timezone.utc), enddatum=datetime(2022, 1, 28, tzinfo=timezone.utc) ) - json_string = zeitraum.json(by_alias=True, ensure_ascii=False) + json_string = zeitraum.model_dump_json(by_alias=True) - assert "2013-05-01T00:00:00+00:00" in json_string - assert "2022-01-28T00:00:00+00:00" in json_string + assert "2013-05-01T00:00:00Z" in json_string + assert "2022-01-28T00:00:00Z" in json_string - zeitraum_deserialized = Zeitraum.parse_raw(json_string) + zeitraum_deserialized = Zeitraum.model_validate_json(json_string) assert isinstance(zeitraum_deserialized.startdatum, datetime) assert zeitraum_deserialized.startdatum == datetime(2013, 5, 1, tzinfo=timezone.utc) @@ -70,12 +70,12 @@ def test_zeitraum_zeitpunkte(self) -> None: endzeitpunkt=datetime(2021, 7, 30, tzinfo=timezone.utc), ) - json_string = zeitraum.json(by_alias=True, ensure_ascii=False) + json_string = zeitraum.model_dump_json(by_alias=True) - assert "2011-02-05T16:43:00+00:00" in json_string - assert "2021-07-30T00:00:00+00:00" in json_string + assert "2011-02-05T16:43:00Z" in json_string + assert "2021-07-30T00:00:00Z" in json_string - zeitraum_deserialized = Zeitraum.parse_raw(json_string) + zeitraum_deserialized = Zeitraum.model_validate_json(json_string) assert isinstance(zeitraum_deserialized.startzeitpunkt, datetime) assert zeitraum_deserialized.startzeitpunkt == datetime(2011, 2, 5, 16, 43, tzinfo=timezone.utc) diff --git a/tests/test_zeitreihenwert.py b/tests/test_zeitreihenwert.py index f865a7f6c..2744aeee9 100644 --- a/tests/test_zeitreihenwert.py +++ b/tests/test_zeitreihenwert.py @@ -22,12 +22,12 @@ def test_zeitreihenwert_only_required_attributes(self) -> None: """ zeitreihenwert = example_zeitreihenwert - json_string = zeitreihenwert.json(by_alias=True, ensure_ascii=False) + json_string = zeitreihenwert.model_dump_json(by_alias=True) - assert "2001-03-15T00:00:00+00:00" in json_string - assert "2007-11-27T00:00:00+00:00" in json_string + assert "2001-03-15T00:00:00Z" in json_string + assert "2007-11-27T00:00:00Z" in json_string - zeitreihenwert_deserialized: Zeitreihenwert = Zeitreihenwert.parse_raw(json_string) + zeitreihenwert_deserialized: Zeitreihenwert = Zeitreihenwert.model_validate_json(json_string) assert zeitreihenwert_deserialized == zeitreihenwert def test_zeitreihenwert_required_and_optional_attributes(self) -> None: @@ -42,15 +42,15 @@ def test_zeitreihenwert_required_and_optional_attributes(self) -> None: statuszusatz=Messwertstatuszusatz.Z78_GERAETEWECHSEL, ) - json_string = zeitreihenwert.json(by_alias=True, ensure_ascii=False) + json_string = zeitreihenwert.model_dump_json(by_alias=True) assert "2.5" in json_string - assert "2001-03-15T00:00:00+00:00" in json_string - assert "2007-11-27T00:00:00+00:00" in json_string + assert "2001-03-15T00:00:00Z" in json_string + assert "2007-11-27T00:00:00Z" in json_string assert "ABGELESEN" in json_string assert "Z78_GERAETEWECHSEL" in json_string - zeitreihenwert_deserialized: Zeitreihenwert = Zeitreihenwert.parse_raw(json_string) + zeitreihenwert_deserialized: Zeitreihenwert = Zeitreihenwert.model_validate_json(json_string) assert zeitreihenwert_deserialized == zeitreihenwert def test_missing_required_attribute(self) -> None: diff --git a/tests/test_zeitreihenwertkompakt.py b/tests/test_zeitreihenwertkompakt.py index dbf637680..226c1c2ce 100644 --- a/tests/test_zeitreihenwertkompakt.py +++ b/tests/test_zeitreihenwertkompakt.py @@ -14,12 +14,12 @@ def test_serialization(self) -> None: wert=Decimal(1.5), status=Messwertstatus.ABGELESEN, statuszusatz=Messwertstatuszusatz.Z78_GERAETEWECHSEL ) - json_string = zrwk.json(by_alias=True, ensure_ascii=False) + json_string = zrwk.model_dump_json(by_alias=True) assert "1.5" in json_string assert "ABGELESEN" in json_string assert "Z78_GERAETEWECHSEL" in json_string - deserialized_zrwk: Zeitreihenwertkompakt = Zeitreihenwertkompakt.parse_raw(json_string) + deserialized_zrwk: Zeitreihenwertkompakt = Zeitreihenwertkompakt.model_validate_json(json_string) assert isinstance(deserialized_zrwk.wert, Decimal) assert deserialized_zrwk.wert == Decimal(1.5) @@ -46,10 +46,10 @@ def test_only_required(self) -> None: wert=Decimal(1.5), ) - json_string = zrwk.json(by_alias=True, ensure_ascii=False) + json_string = zrwk.model_dump_json(by_alias=True) assert "1.5" in json_string - deserialized_zrwk: Zeitreihenwertkompakt = Zeitreihenwertkompakt.parse_raw(json_string) + deserialized_zrwk: Zeitreihenwertkompakt = Zeitreihenwertkompakt.model_validate_json(json_string) assert deserialized_zrwk == zrwk diff --git a/tox.ini b/tox.ini index 5c0a6c019..70ba5c91a 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,7 @@ usedevelop = True # the type_check environment checks the type hints using mypy deps = -rrequirements.txt - mypy==1.3.0 + mypy pytest commands = mypy --show-error-codes src/bo4e