Skip to content

Commit

Permalink
Merge pull request #306 from Blueprints-org/305-feature-request-abc-e…
Browse files Browse the repository at this point in the history
…xposure-class

Add ABCEnumMeta to be able to create Abstract Enum classes + Refactor Exposure classes and adjust tests
  • Loading branch information
SZeltaat authored Jul 11, 2024
2 parents c5b8544 + 5e25eac commit 67b008d
Show file tree
Hide file tree
Showing 9 changed files with 612 additions and 256 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.7
rev: v0.5.1
hooks:
# Run the linter.
- id: ruff
Expand All @@ -26,7 +26,7 @@ repos:
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
rev: v1.10.1
hooks:
- id: mypy
language_version: python3.11
166 changes: 166 additions & 0 deletions blueprints/codes/eurocode/exposure_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""Module for the concrete exposure classes
according to Table 4.1 from NEN-EN 1992-1-1: Chapter 4 - Durability and cover to reinforcement.
"""

from abc import abstractmethod
from enum import Enum
from functools import total_ordering
from typing import NamedTuple, Type, TypeVar

from blueprints.utils.abc_enum_meta import ABCEnumMeta

T = TypeVar("T", bound="Exposure")


@total_ordering
class Exposure(Enum, metaclass=ABCEnumMeta):
"""Parent class for individual exposure classes.
This class handles the ordering/comparison operations, that's why it is decorated with total_ordering (As recommended by PEP8).
On top of that, it handles a couple of methods which will be used by its subclasses.
"""

def __eq__(self, other: object) -> bool:
"""Definition of '==' operator for the comparison of the severity of the exposure classifications.
Parameters
----------
self : Exposure/ subclass of Exposure
First argument for the comparison.
other : object
Second argument for the comparison.
Raises
------
TypeError
If different types are being compared.
Returns
-------
Boolean
True if both arguments are of the same severity (In this case they will both be literally the same).
"""
if isinstance(other, self.__class__):
_self_severity = int(self.value[-1]) if self.value != "Not applicable" else 0
_other_severity = int(other.value[-1]) if other.value != "Not applicable" else 0
return _self_severity == _other_severity
raise TypeError("Only the same exposure class types can be compared with each other!")

def __gt__(self, other: object) -> bool:
"""Definition of '>' operator for the comparison of the severity of the exposure classifications.
Parameters
----------
self : Exposure/ subclass of Exposure
First argument for the comparison.
other : object
Second argument for the comparison.
Raises
------
TypeError
If different types are being compared.
Returns
-------
Boolean
True if the first argument is more severe than the second argument.
"""
if isinstance(other, self.__class__):
_self_severity = int(self.value[-1]) if self.value != "Not applicable" else 0
_other_severity = int(other.value[-1]) if other.value != "Not applicable" else 0
return _self_severity > _other_severity
raise TypeError("Only the same exposure class types can be compared with each other!")

@classmethod
def options(cls: Type[T]) -> list[str]:
"""Return all the possible options within a subclass.
Returns
-------
list[str]
all the possible class designations within a specific exposure class
"""
return [m._value_ for m in cls.__members__.values()]

@staticmethod
@abstractmethod
def exposure_class_description() -> str:
"""Description of subclasses to be implemented in each subclass.
Returns
-------
str
description of the specific exposure class
"""

@abstractmethod
def description_of_the_environment(self) -> str:
"""Description of the environment based on the instance.
Returns
-------
str
description of the environment based on the instance
"""


@total_ordering
class CarbonationBase(Exposure):
"""Enum Class which indicates the classification of corrosion induced by carbonation."""


@total_ordering
class ChlorideBase(Exposure):
"""Enum Class which indicates the classification of corrosion induced by chlorides other than by sea water."""


@total_ordering
class ChlorideSeawaterBase(Exposure):
"""Enum Class which indicates the classification of corrosion induced by chlorides from sea water."""


@total_ordering
class FreezeThawBase(Exposure):
"""Enum Class which indicates the classification of freeze/thaw attack with or without de-icing agents."""


@total_ordering
class ChemicalBase(Exposure):
"""Enum Class which indicates the classification of chemical attack."""


class ExposureClassesBase(NamedTuple):
"""Parent class which serves as a container for the Exposure classes.
Exposure classes related to environmental conditions in accordance with EN 206-1
"""

carbonation: CarbonationBase
chloride: ChlorideBase
chloride_seawater: ChlorideSeawaterBase
freeze: FreezeThawBase
chemical: ChemicalBase

@property
def no_risk(self) -> bool:
"""Check if all exposure classes are 'Not applicable'.
This represents X0 class designation according to table 4.1 from NEN-EN 1992-1-1+C2:2011.
Returns
-------
bool
True if all exposure classes are 'Not applicable'
"""
return all(exposure_class.value == "Not applicable" for exposure_class in self)

def __str__(self) -> str:
"""String representation of the ExposureClasses object.
Returns
-------
str
String representation of the ExposureClasses object
"""
return "X0" if self.no_risk else ", ".join(enum.value for enum in self if enum.value != "Not applicable")
Original file line number Diff line number Diff line change
Expand Up @@ -2,108 +2,20 @@
according to Table 4.1 from NEN-EN 1992-1-1+C2:2011: Chapter 4 - Durability and cover to reinforcement.
"""

from enum import Enum
from functools import total_ordering
from typing import NamedTuple, Type, TypeVar

T = TypeVar("T", bound="Exposure")
from blueprints.codes.eurocode.exposure_classes import (
CarbonationBase,
ChemicalBase,
ChlorideBase,
ChlorideSeawaterBase,
ExposureClassesBase,
FreezeThawBase,
)


@total_ordering
class Exposure(Enum):
"""Parent class for individual exposure classes.
This class handles the ordering/comparison operations, that's why it is decorated with total_ordering (As recommended by PEP8).
On top of that, it handles a couple of methods which will be used by its subclasses.
"""

def __eq__(self, other: object) -> bool:
"""Definition of '==' operator for the comparison of the severity of the exposure classifications.
Parameters
----------
self : Exposure/ subclass of Exposure
First argument for the comparison.
other : object
Second argument for the comparison.
Raises
------
TypeError
If different types are being compared.
Returns
-------
Boolean
True if both arguments are of the same severity (In this case they will both be literally the same).
"""
if isinstance(other, self.__class__):
_self_severity = int(self.value[-1]) if self.value != "Not applicable" else 0
_other_severity = int(other.value[-1]) if other.value != "Not applicable" else 0
return _self_severity == _other_severity
raise TypeError("Only the same exposure class types can be compared with each other!")

def __gt__(self, other: object) -> bool:
"""Definition of '>' operator for the comparison of the severity of the exposure classifications.
Parameters
----------
self : Exposure/ subclass of Exposure
First argument for the comparison.
other : object
Second argument for the comparison.
Raises
------
TypeError
If different types are being compared.
Returns
-------
Boolean
True if the first argument is more severe than the second argument.
"""
if isinstance(other, self.__class__):
_self_severity = int(self.value[-1]) if self.value != "Not applicable" else 0
_other_severity = int(other.value[-1]) if other.value != "Not applicable" else 0
return _self_severity > _other_severity
raise TypeError("Only the same exposure class types can be compared with each other!")

@classmethod
def options(cls: Type[T]) -> list[str]:
"""Return all the possible options within a subclass.
Returns
-------
list[str]
all the possible class designations within a specific exposure class
"""
return [m._value_ for m in cls.__members__.values()]

@staticmethod
def exposure_class_description() -> str:
"""Description of subclasses to be implemented in each subclass.
Returns
-------
str
description of the specific exposure class
"""
raise NotImplementedError("The description method must be implemented in the subclass!")

def description_of_the_environment(self) -> str:
"""Description of the environment based on the instance.
Returns
-------
str
description of the environment based on the instance
"""
raise NotImplementedError("The description_of_the_environment method must be implemented in the subclass!")


@total_ordering
class Carbonation(Exposure):
class Carbonation(CarbonationBase):
"""Enum Class which indicates the classification of corrosion induced by carbonation."""

NA = "Not applicable"
Expand Down Expand Up @@ -145,7 +57,7 @@ def description_of_the_environment(self) -> str:


@total_ordering
class Chloride(Exposure):
class Chloride(ChlorideBase):
"""Enum Class which indicates the classification of corrosion induced by chlorides other than by sea water."""

NA = "Not applicable"
Expand Down Expand Up @@ -184,7 +96,7 @@ def description_of_the_environment(self) -> str:


@total_ordering
class ChlorideSeawater(Exposure):
class ChlorideSeawater(ChlorideSeawaterBase):
"""Enum Class which indicates the classification of corrosion induced by chlorides from sea water."""

NA = "Not applicable"
Expand Down Expand Up @@ -223,7 +135,7 @@ def description_of_the_environment(self) -> str:


@total_ordering
class FreezeThaw(Exposure):
class FreezeThaw(FreezeThawBase):
"""Enum Class which indicates the classification of freeze/thaw attack with or without de-icing agents."""

NA = "Not applicable"
Expand Down Expand Up @@ -265,7 +177,7 @@ def description_of_the_environment(self) -> str:


@total_ordering
class Chemical(Exposure):
class Chemical(ChemicalBase):
"""Enum Class which indicates the classification of chemical attack."""

NA = "Not applicable"
Expand Down Expand Up @@ -303,39 +215,14 @@ def description_of_the_environment(self) -> str:
return "Not applicable"


class ExposureClasses(NamedTuple):
"""Implementation of tabel 4.1 from NEN-EN 1992-1-1+C2:2011.
class ExposureClasses(ExposureClassesBase):
"""Implementation of table 4.1 from NEN-EN 1992-1-1+C2:2011.
Exposure classes related to environmental conditions in accordance with EN 206-1
"""

carbonation: Carbonation = Carbonation("Not applicable")
chloride: Chloride = Chloride("Not applicable")
chloride_seawater: ChlorideSeawater = ChlorideSeawater("Not applicable")
freeze: FreezeThaw = FreezeThaw("Not applicable")
chemical: Chemical = Chemical("Not applicable")

def __str__(self) -> str:
"""String representation of the ExposureClasses object.
Returns
-------
str
String representation of the ExposureClasses object
"""
if self.no_risk:
return "X0"
return ", ".join(enum.value for enum in self if enum.value != "Not applicable")

@property
def no_risk(self) -> bool:
"""Check if all exposure classes are 'Not applicable'.
This represents X0 class designation according to table 4.1 from NEN-EN 1992-1-1+C2:2011.
Returns
-------
bool
True if all exposure classes are 'Not applicable'
"""
return all(exposure_class.value == "Not applicable" for exposure_class in self)
carbonation: Carbonation
chloride: Chloride
chloride_seawater: ChlorideSeawater
freeze: FreezeThaw
chemical: Chemical
1 change: 1 addition & 0 deletions blueprints/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Utils blueprint."""
Loading

0 comments on commit 67b008d

Please sign in to comment.