diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 889aeab6..0ca583b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml args: ['--unsafe'] @@ -22,9 +22,3 @@ repos: types: [python] language: system pass_filenames: false - - id: pyupgrade - name: Pyupgrade - entry: pyupgrade --py37-plus - types: [python] - language: system - exclude: "pydantic_extra_types/types/phone_numbers.py" diff --git a/Makefile b/Makefile index 596b4ed6..f25b84fe 100644 --- a/Makefile +++ b/Makefile @@ -18,22 +18,18 @@ refresh-lockfiles: .PHONY: format format: - black $(sources) ruff --fix $(sources) + ruff format $(sources) .PHONY: lint lint: ruff $(sources) - black $(sources) --check --diff + ruff format --check $(sources) .PHONY: mypy mypy: mypy pydantic_extra_types -.PHONY: pyupgrade -pyupgrade: - pyupgrade --py37-plus `find pydantic_extra_types tests -name "*.py" -type f` - .PHONY: test test: coverage run -m pytest --durations=10 diff --git a/pydantic_extra_types/__init__.py b/pydantic_extra_types/__init__.py index e59b17b4..50062f87 100644 --- a/pydantic_extra_types/__init__.py +++ b/pydantic_extra_types/__init__.py @@ -1 +1 @@ -__version__ = '2.5.0' +__version__ = "2.5.0" diff --git a/pydantic_extra_types/color.py b/pydantic_extra_types/color.py index 5229adbc..7f5c90c8 100644 --- a/pydantic_extra_types/color.py +++ b/pydantic_extra_types/color.py @@ -20,7 +20,7 @@ from pydantic_core import CoreSchema, PydanticCustomError, core_schema ColorTuple = Union[Tuple[int, int, int], Tuple[int, int, int, float]] -ColorType = Union[ColorTuple, str, 'Color'] +ColorType = Union[ColorTuple, str, "Color"] HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, float]] @@ -29,7 +29,7 @@ class RGBA: Internal use only as a representation of a color. """ - __slots__ = 'r', 'g', 'b', 'alpha', '_tuple' + __slots__ = "r", "g", "b", "alpha", "_tuple" def __init__(self, r: float, g: float, b: float, alpha: float | None): self.r = r @@ -44,24 +44,24 @@ def __getitem__(self, item: Any) -> Any: # these are not compiled here to avoid import slowdown, they'll be compiled the first time they're used, then cached -_r_255 = r'(\d{1,3}(?:\.\d+)?)' -_r_comma = r'\s*,\s*' -_r_alpha = r'(\d(?:\.\d+)?|\.\d+|\d{1,2}%)' -_r_h = r'(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?' -_r_sl = r'(\d{1,3}(?:\.\d+)?)%' -r_hex_short = r'\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*' -r_hex_long = r'\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*' +_r_255 = r"(\d{1,3}(?:\.\d+)?)" +_r_comma = r"\s*,\s*" +_r_alpha = r"(\d(?:\.\d+)?|\.\d+|\d{1,2}%)" +_r_h = r"(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?" +_r_sl = r"(\d{1,3}(?:\.\d+)?)%" +r_hex_short = r"\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*" +r_hex_long = r"\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*" # CSS3 RGB examples: rgb(0, 0, 0), rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 50%) -r_rgb = rf'\s*rgba?\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}(?:{_r_comma}{_r_alpha})?\s*\)\s*' +r_rgb = rf"\s*rgba?\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}(?:{_r_comma}{_r_alpha})?\s*\)\s*" # CSS3 HSL examples: hsl(270, 60%, 50%), hsla(270, 60%, 50%, 0.5), hsla(270, 60%, 50%, 50%) -r_hsl = rf'\s*hsla?\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}(?:{_r_comma}{_r_alpha})?\s*\)\s*' +r_hsl = rf"\s*hsla?\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}(?:{_r_comma}{_r_alpha})?\s*\)\s*" # CSS4 RGB examples: rgb(0 0 0), rgb(0 0 0 / 0.5), rgb(0 0 0 / 50%), rgba(0 0 0 / 50%) -r_rgb_v4_style = rf'\s*rgba?\(\s*{_r_255}\s+{_r_255}\s+{_r_255}(?:\s*/\s*{_r_alpha})?\s*\)\s*' +r_rgb_v4_style = rf"\s*rgba?\(\s*{_r_255}\s+{_r_255}\s+{_r_255}(?:\s*/\s*{_r_alpha})?\s*\)\s*" # CSS4 HSL examples: hsl(270 60% 50%), hsl(270 60% 50% / 0.5), hsl(270 60% 50% / 50%), hsla(270 60% 50% / 50%) -r_hsl_v4_style = rf'\s*hsla?\(\s*{_r_h}\s+{_r_sl}\s+{_r_sl}(?:\s*/\s*{_r_alpha})?\s*\)\s*' +r_hsl_v4_style = rf"\s*hsla?\(\s*{_r_h}\s+{_r_sl}\s+{_r_sl}(?:\s*/\s*{_r_alpha})?\s*\)\s*" # colors where the two hex characters are the same, if all colors match this the short version of hex colors can be used -repeat_colors = {int(c * 2, 16) for c in '0123456789abcdef'} +repeat_colors = {int(c * 2, 16) for c in "0123456789abcdef"} rads = 2 * math.pi @@ -70,7 +70,7 @@ class Color(_repr.Representation): Represents a color. """ - __slots__ = '_original', '_rgba' + __slots__ = "_original", "_rgba" def __init__(self, value: ColorType) -> None: self._rgba: RGBA @@ -84,8 +84,8 @@ def __init__(self, value: ColorType) -> None: value = value._original else: raise PydanticCustomError( - 'color_error', - 'value is not a valid color: value must be a tuple, list or string', + "color_error", + "value is not a valid color: value must be a tuple, list or string", ) # if we've got here value must be a valid color @@ -96,7 +96,7 @@ def __get_pydantic_json_schema__( cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler ) -> JsonSchemaValue: field_schema: dict[str, Any] = {} - field_schema.update(type='string', format='color') + field_schema.update(type="string", format="color") return field_schema def original(self) -> ColorType: @@ -128,11 +128,11 @@ def as_named(self, *, fallback: bool = False) -> str: if fallback: return self.as_hex() else: - raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e + raise ValueError("no named color found, use fallback=True, as_hex() or as_rgb()") from e else: return self.as_hex() - def as_hex(self, format: Literal['short', 'long'] = 'short') -> str: + def as_hex(self, format: Literal["short", "long"] = "short") -> str: """Returns the hexadecimal representation of the color. Hex string representing the color can be 3, 4, 6, or 8 characters depending on whether the string @@ -145,21 +145,21 @@ def as_hex(self, format: Literal['short', 'long'] = 'short') -> str: if self._rgba.alpha is not None: values.append(float_to_255(self._rgba.alpha)) - as_hex = ''.join(f'{v:02x}' for v in values) - if format == 'short' and all(c in repeat_colors for c in values): - as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2)) - return '#' + as_hex + as_hex = "".join(f"{v:02x}" for v in values) + if format == "short" and all(c in repeat_colors for c in values): + as_hex = "".join(as_hex[c] for c in range(0, len(as_hex), 2)) + return "#" + as_hex def as_rgb(self) -> str: """ Color as an `rgb(, , )` or `rgba(, , , )` string. """ if self._rgba.alpha is None: - return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})' + return f"rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})" else: return ( - f'rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, ' - f'{round(self._alpha_float(), 2)})' + f"rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, " + f"{round(self._alpha_float(), 2)})" ) def as_rgb_tuple(self, *, alpha: bool | None = None) -> ColorTuple: @@ -195,10 +195,10 @@ def as_hsl(self) -> str: """ if self._rgba.alpha is None: h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore - return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})' + return f"hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})" else: h, s, li, a = self.as_hsl_tuple(alpha=True) # type: ignore - return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})' + return f"hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})" def as_hsl_tuple(self, *, alpha: bool | None = None) -> HslColorTuple: """ @@ -218,7 +218,7 @@ def as_hsl_tuple(self, *, alpha: bool | None = None) -> HslColorTuple: Note: This is HSL as used in HTML and most other places, not HLS as used in Python's `colorsys`. """ - h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b) # noqa: E741 + h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b) if alpha is None: if self._rgba.alpha is None: return h, s, l @@ -249,7 +249,7 @@ def __str__(self) -> str: return self.as_named(fallback=True) def __repr_args__(self) -> _repr.ReprArgs: - return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] + return [(None, self.as_named(fallback=True))] + [("rgb", self.as_rgb_tuple())] def __eq__(self, other: Any) -> bool: return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple() @@ -277,7 +277,7 @@ def parse_tuple(value: tuple[Any, ...]) -> RGBA: r, g, b = (parse_color_value(v) for v in value[:3]) return RGBA(r, g, b, parse_float_alpha(value[3])) else: - raise PydanticCustomError('color_error', 'value is not a valid color: tuples must have length 3 or 4') + raise PydanticCustomError("color_error", "value is not a valid color: tuples must have length 3 or 4") def parse_str(value: str) -> RGBA: @@ -338,12 +338,12 @@ def parse_str(value: str) -> RGBA: if m: return parse_hsl(*m.groups()) # type: ignore - if value_lower == 'transparent': + if value_lower == "transparent": return RGBA(0, 0, 0, 0) raise PydanticCustomError( - 'color_error', - 'value is not a valid color: string not recognised as a valid color', + "color_error", + "value is not a valid color: string not recognised as a valid color", ) @@ -391,16 +391,16 @@ def parse_color_value(value: int | str, max_val: int = 255) -> float: color = float(value) except ValueError: raise PydanticCustomError( - 'color_error', - 'value is not a valid color: color values must be a valid number', + "color_error", + "value is not a valid color: color values must be a valid number", ) if 0 <= color <= max_val: return color / max_val else: raise PydanticCustomError( - 'color_error', - 'value is not a valid color: color values must be in the range 0 to {max_val}', - {'max_val': max_val}, + "color_error", + "value is not a valid color: color values must be in the range 0 to {max_val}", + {"max_val": max_val}, ) @@ -420,14 +420,14 @@ def parse_float_alpha(value: None | str | float | int) -> float | None: if value is None: return None try: - if isinstance(value, str) and value.endswith('%'): + if isinstance(value, str) and value.endswith("%"): alpha = float(value[:-1]) / 100 else: alpha = float(value) except ValueError: raise PydanticCustomError( - 'color_error', - 'value is not a valid color: alpha values must be a valid float', + "color_error", + "value is not a valid color: alpha values must be a valid float", ) if math.isclose(alpha, 1): @@ -436,8 +436,8 @@ def parse_float_alpha(value: None | str | float | int) -> float | None: return alpha else: raise PydanticCustomError( - 'color_error', - 'value is not a valid color: alpha values must be in the range 0 to 1', + "color_error", + "value is not a valid color: alpha values must be in the range 0 to 1", ) @@ -458,9 +458,9 @@ def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: float | None = s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100) h_value = float(h) - if h_units in {None, 'deg'}: + if h_units in {None, "deg"}: h_value = h_value % 360 / 360 - elif h_units == 'rad': + elif h_units == "rad": h_value = h_value % rads / rads else: # turns @@ -484,153 +484,153 @@ def float_to_255(c: float) -> int: COLORS_BY_NAME = { - 'aliceblue': (240, 248, 255), - 'antiquewhite': (250, 235, 215), - 'aqua': (0, 255, 255), - 'aquamarine': (127, 255, 212), - 'azure': (240, 255, 255), - 'beige': (245, 245, 220), - 'bisque': (255, 228, 196), - 'black': (0, 0, 0), - 'blanchedalmond': (255, 235, 205), - 'blue': (0, 0, 255), - 'blueviolet': (138, 43, 226), - 'brown': (165, 42, 42), - 'burlywood': (222, 184, 135), - 'cadetblue': (95, 158, 160), - 'chartreuse': (127, 255, 0), - 'chocolate': (210, 105, 30), - 'coral': (255, 127, 80), - 'cornflowerblue': (100, 149, 237), - 'cornsilk': (255, 248, 220), - 'crimson': (220, 20, 60), - 'cyan': (0, 255, 255), - 'darkblue': (0, 0, 139), - 'darkcyan': (0, 139, 139), - 'darkgoldenrod': (184, 134, 11), - 'darkgray': (169, 169, 169), - 'darkgreen': (0, 100, 0), - 'darkgrey': (169, 169, 169), - 'darkkhaki': (189, 183, 107), - 'darkmagenta': (139, 0, 139), - 'darkolivegreen': (85, 107, 47), - 'darkorange': (255, 140, 0), - 'darkorchid': (153, 50, 204), - 'darkred': (139, 0, 0), - 'darksalmon': (233, 150, 122), - 'darkseagreen': (143, 188, 143), - 'darkslateblue': (72, 61, 139), - 'darkslategray': (47, 79, 79), - 'darkslategrey': (47, 79, 79), - 'darkturquoise': (0, 206, 209), - 'darkviolet': (148, 0, 211), - 'deeppink': (255, 20, 147), - 'deepskyblue': (0, 191, 255), - 'dimgray': (105, 105, 105), - 'dimgrey': (105, 105, 105), - 'dodgerblue': (30, 144, 255), - 'firebrick': (178, 34, 34), - 'floralwhite': (255, 250, 240), - 'forestgreen': (34, 139, 34), - 'fuchsia': (255, 0, 255), - 'gainsboro': (220, 220, 220), - 'ghostwhite': (248, 248, 255), - 'gold': (255, 215, 0), - 'goldenrod': (218, 165, 32), - 'gray': (128, 128, 128), - 'green': (0, 128, 0), - 'greenyellow': (173, 255, 47), - 'grey': (128, 128, 128), - 'honeydew': (240, 255, 240), - 'hotpink': (255, 105, 180), - 'indianred': (205, 92, 92), - 'indigo': (75, 0, 130), - 'ivory': (255, 255, 240), - 'khaki': (240, 230, 140), - 'lavender': (230, 230, 250), - 'lavenderblush': (255, 240, 245), - 'lawngreen': (124, 252, 0), - 'lemonchiffon': (255, 250, 205), - 'lightblue': (173, 216, 230), - 'lightcoral': (240, 128, 128), - 'lightcyan': (224, 255, 255), - 'lightgoldenrodyellow': (250, 250, 210), - 'lightgray': (211, 211, 211), - 'lightgreen': (144, 238, 144), - 'lightgrey': (211, 211, 211), - 'lightpink': (255, 182, 193), - 'lightsalmon': (255, 160, 122), - 'lightseagreen': (32, 178, 170), - 'lightskyblue': (135, 206, 250), - 'lightslategray': (119, 136, 153), - 'lightslategrey': (119, 136, 153), - 'lightsteelblue': (176, 196, 222), - 'lightyellow': (255, 255, 224), - 'lime': (0, 255, 0), - 'limegreen': (50, 205, 50), - 'linen': (250, 240, 230), - 'magenta': (255, 0, 255), - 'maroon': (128, 0, 0), - 'mediumaquamarine': (102, 205, 170), - 'mediumblue': (0, 0, 205), - 'mediumorchid': (186, 85, 211), - 'mediumpurple': (147, 112, 219), - 'mediumseagreen': (60, 179, 113), - 'mediumslateblue': (123, 104, 238), - 'mediumspringgreen': (0, 250, 154), - 'mediumturquoise': (72, 209, 204), - 'mediumvioletred': (199, 21, 133), - 'midnightblue': (25, 25, 112), - 'mintcream': (245, 255, 250), - 'mistyrose': (255, 228, 225), - 'moccasin': (255, 228, 181), - 'navajowhite': (255, 222, 173), - 'navy': (0, 0, 128), - 'oldlace': (253, 245, 230), - 'olive': (128, 128, 0), - 'olivedrab': (107, 142, 35), - 'orange': (255, 165, 0), - 'orangered': (255, 69, 0), - 'orchid': (218, 112, 214), - 'palegoldenrod': (238, 232, 170), - 'palegreen': (152, 251, 152), - 'paleturquoise': (175, 238, 238), - 'palevioletred': (219, 112, 147), - 'papayawhip': (255, 239, 213), - 'peachpuff': (255, 218, 185), - 'peru': (205, 133, 63), - 'pink': (255, 192, 203), - 'plum': (221, 160, 221), - 'powderblue': (176, 224, 230), - 'purple': (128, 0, 128), - 'red': (255, 0, 0), - 'rosybrown': (188, 143, 143), - 'royalblue': (65, 105, 225), - 'saddlebrown': (139, 69, 19), - 'salmon': (250, 128, 114), - 'sandybrown': (244, 164, 96), - 'seagreen': (46, 139, 87), - 'seashell': (255, 245, 238), - 'sienna': (160, 82, 45), - 'silver': (192, 192, 192), - 'skyblue': (135, 206, 235), - 'slateblue': (106, 90, 205), - 'slategray': (112, 128, 144), - 'slategrey': (112, 128, 144), - 'snow': (255, 250, 250), - 'springgreen': (0, 255, 127), - 'steelblue': (70, 130, 180), - 'tan': (210, 180, 140), - 'teal': (0, 128, 128), - 'thistle': (216, 191, 216), - 'tomato': (255, 99, 71), - 'turquoise': (64, 224, 208), - 'violet': (238, 130, 238), - 'wheat': (245, 222, 179), - 'white': (255, 255, 255), - 'whitesmoke': (245, 245, 245), - 'yellow': (255, 255, 0), - 'yellowgreen': (154, 205, 50), + "aliceblue": (240, 248, 255), + "antiquewhite": (250, 235, 215), + "aqua": (0, 255, 255), + "aquamarine": (127, 255, 212), + "azure": (240, 255, 255), + "beige": (245, 245, 220), + "bisque": (255, 228, 196), + "black": (0, 0, 0), + "blanchedalmond": (255, 235, 205), + "blue": (0, 0, 255), + "blueviolet": (138, 43, 226), + "brown": (165, 42, 42), + "burlywood": (222, 184, 135), + "cadetblue": (95, 158, 160), + "chartreuse": (127, 255, 0), + "chocolate": (210, 105, 30), + "coral": (255, 127, 80), + "cornflowerblue": (100, 149, 237), + "cornsilk": (255, 248, 220), + "crimson": (220, 20, 60), + "cyan": (0, 255, 255), + "darkblue": (0, 0, 139), + "darkcyan": (0, 139, 139), + "darkgoldenrod": (184, 134, 11), + "darkgray": (169, 169, 169), + "darkgreen": (0, 100, 0), + "darkgrey": (169, 169, 169), + "darkkhaki": (189, 183, 107), + "darkmagenta": (139, 0, 139), + "darkolivegreen": (85, 107, 47), + "darkorange": (255, 140, 0), + "darkorchid": (153, 50, 204), + "darkred": (139, 0, 0), + "darksalmon": (233, 150, 122), + "darkseagreen": (143, 188, 143), + "darkslateblue": (72, 61, 139), + "darkslategray": (47, 79, 79), + "darkslategrey": (47, 79, 79), + "darkturquoise": (0, 206, 209), + "darkviolet": (148, 0, 211), + "deeppink": (255, 20, 147), + "deepskyblue": (0, 191, 255), + "dimgray": (105, 105, 105), + "dimgrey": (105, 105, 105), + "dodgerblue": (30, 144, 255), + "firebrick": (178, 34, 34), + "floralwhite": (255, 250, 240), + "forestgreen": (34, 139, 34), + "fuchsia": (255, 0, 255), + "gainsboro": (220, 220, 220), + "ghostwhite": (248, 248, 255), + "gold": (255, 215, 0), + "goldenrod": (218, 165, 32), + "gray": (128, 128, 128), + "green": (0, 128, 0), + "greenyellow": (173, 255, 47), + "grey": (128, 128, 128), + "honeydew": (240, 255, 240), + "hotpink": (255, 105, 180), + "indianred": (205, 92, 92), + "indigo": (75, 0, 130), + "ivory": (255, 255, 240), + "khaki": (240, 230, 140), + "lavender": (230, 230, 250), + "lavenderblush": (255, 240, 245), + "lawngreen": (124, 252, 0), + "lemonchiffon": (255, 250, 205), + "lightblue": (173, 216, 230), + "lightcoral": (240, 128, 128), + "lightcyan": (224, 255, 255), + "lightgoldenrodyellow": (250, 250, 210), + "lightgray": (211, 211, 211), + "lightgreen": (144, 238, 144), + "lightgrey": (211, 211, 211), + "lightpink": (255, 182, 193), + "lightsalmon": (255, 160, 122), + "lightseagreen": (32, 178, 170), + "lightskyblue": (135, 206, 250), + "lightslategray": (119, 136, 153), + "lightslategrey": (119, 136, 153), + "lightsteelblue": (176, 196, 222), + "lightyellow": (255, 255, 224), + "lime": (0, 255, 0), + "limegreen": (50, 205, 50), + "linen": (250, 240, 230), + "magenta": (255, 0, 255), + "maroon": (128, 0, 0), + "mediumaquamarine": (102, 205, 170), + "mediumblue": (0, 0, 205), + "mediumorchid": (186, 85, 211), + "mediumpurple": (147, 112, 219), + "mediumseagreen": (60, 179, 113), + "mediumslateblue": (123, 104, 238), + "mediumspringgreen": (0, 250, 154), + "mediumturquoise": (72, 209, 204), + "mediumvioletred": (199, 21, 133), + "midnightblue": (25, 25, 112), + "mintcream": (245, 255, 250), + "mistyrose": (255, 228, 225), + "moccasin": (255, 228, 181), + "navajowhite": (255, 222, 173), + "navy": (0, 0, 128), + "oldlace": (253, 245, 230), + "olive": (128, 128, 0), + "olivedrab": (107, 142, 35), + "orange": (255, 165, 0), + "orangered": (255, 69, 0), + "orchid": (218, 112, 214), + "palegoldenrod": (238, 232, 170), + "palegreen": (152, 251, 152), + "paleturquoise": (175, 238, 238), + "palevioletred": (219, 112, 147), + "papayawhip": (255, 239, 213), + "peachpuff": (255, 218, 185), + "peru": (205, 133, 63), + "pink": (255, 192, 203), + "plum": (221, 160, 221), + "powderblue": (176, 224, 230), + "purple": (128, 0, 128), + "red": (255, 0, 0), + "rosybrown": (188, 143, 143), + "royalblue": (65, 105, 225), + "saddlebrown": (139, 69, 19), + "salmon": (250, 128, 114), + "sandybrown": (244, 164, 96), + "seagreen": (46, 139, 87), + "seashell": (255, 245, 238), + "sienna": (160, 82, 45), + "silver": (192, 192, 192), + "skyblue": (135, 206, 235), + "slateblue": (106, 90, 205), + "slategray": (112, 128, 144), + "slategrey": (112, 128, 144), + "snow": (255, 250, 250), + "springgreen": (0, 255, 127), + "steelblue": (70, 130, 180), + "tan": (210, 180, 140), + "teal": (0, 128, 128), + "thistle": (216, 191, 216), + "tomato": (255, 99, 71), + "turquoise": (64, 224, 208), + "violet": (238, 130, 238), + "wheat": (245, 222, 179), + "white": (255, 255, 255), + "whitesmoke": (245, 245, 245), + "yellow": (255, 255, 0), + "yellowgreen": (154, 205, 50), } COLORS_BY_VALUE = {v: k for k, v in COLORS_BY_NAME.items()} diff --git a/pydantic_extra_types/coordinate.py b/pydantic_extra_types/coordinate.py index 0fd4405c..85ef5ccb 100644 --- a/pydantic_extra_types/coordinate.py +++ b/pydantic_extra_types/coordinate.py @@ -102,7 +102,8 @@ def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaH chain_length = len(schema_chain) chain_schemas = [core_schema.chain_schema(schema_chain[x:]) for x in range(chain_length - 1, -1, -1)] return core_schema.no_info_wrap_validator_function( - cls._parse_args, core_schema.union_schema(chain_schemas) # type: ignore[arg-type] + cls._parse_args, + core_schema.union_schema(chain_schemas), # type: ignore[arg-type] ) @classmethod @@ -120,11 +121,11 @@ def _parse_str(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandle if not isinstance(value, str): return value try: - value = tuple(float(x) for x in value.split(',')) + value = tuple(float(x) for x in value.split(",")) except ValueError: raise PydanticCustomError( - 'coordinate_error', - 'value is not a valid coordinate: string is not recognized as a valid coordinate', + "coordinate_error", + "value is not a valid coordinate: string is not recognized as a valid coordinate", ) return ArgsKwargs(args=value) @@ -135,7 +136,7 @@ def _parse_tuple(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHand return ArgsKwargs(args=handler(value)) def __str__(self) -> str: - return f'{self.latitude},{self.longitude}' + return f"{self.latitude},{self.longitude}" def __eq__(self, other: Any) -> bool: return isinstance(other, Coordinate) and self.latitude == other.latitude and self.longitude == other.longitude diff --git a/pydantic_extra_types/country.py b/pydantic_extra_types/country.py index a6d26e2e..263ae530 100644 --- a/pydantic_extra_types/country.py +++ b/pydantic_extra_types/country.py @@ -80,7 +80,7 @@ class Product(BaseModel): @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryAlpha2: if __input_value not in _index_by_alpha2(): - raise PydanticCustomError('country_alpha2', 'Invalid country alpha2 code') + raise PydanticCustomError("country_alpha2", "Invalid country alpha2 code") return cls(__input_value) @classmethod @@ -97,7 +97,7 @@ def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler ) -> dict[str, Any]: json_schema = handler(schema) - json_schema.update({'pattern': r'^\w{2}$'}) + json_schema.update({"pattern": r"^\w{2}$"}) return json_schema @property @@ -137,7 +137,7 @@ class Product(BaseModel): @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryAlpha3: if __input_value not in _index_by_alpha3(): - raise PydanticCustomError('country_alpha3', 'Invalid country alpha3 code') + raise PydanticCustomError("country_alpha3", "Invalid country alpha3 code") return cls(__input_value) @classmethod @@ -155,7 +155,7 @@ def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler ) -> dict[str, Any]: json_schema = handler(schema) - json_schema.update({'pattern': r'^\w{3}$'}) + json_schema.update({"pattern": r"^\w{3}$"}) return json_schema @property @@ -195,7 +195,7 @@ class Product(BaseModel): @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryNumericCode: if __input_value not in _index_by_numeric_code(): - raise PydanticCustomError('country_numeric_code', 'Invalid country numeric code') + raise PydanticCustomError("country_numeric_code", "Invalid country numeric code") return cls(__input_value) @classmethod @@ -213,7 +213,7 @@ def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler ) -> dict[str, Any]: json_schema = handler(schema) - json_schema.update({'pattern': r'^[0-9]{3}$'}) + json_schema.update({"pattern": r"^[0-9]{3}$"}) return json_schema @property @@ -252,7 +252,7 @@ class Product(BaseModel): @classmethod def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> CountryShortName: if __input_value not in _index_by_short_name(): - raise PydanticCustomError('country_short_name', 'Invalid country short name') + raise PydanticCustomError("country_short_name", "Invalid country short name") return cls(__input_value) @classmethod diff --git a/pydantic_extra_types/currency_code.py b/pydantic_extra_types/currency_code.py index c19d9bf2..344d97c3 100644 --- a/pydantic_extra_types/currency_code.py +++ b/pydantic_extra_types/currency_code.py @@ -18,17 +18,17 @@ # List of codes that should not be usually used within regular transactions _CODES_FOR_BONDS_METAL_TESTING = { - 'XTS', # testing - 'XAU', # gold - 'XAG', # silver - 'XPD', # palladium - 'XPT', # platinum - 'XBA', # Bond Markets Unit European Composite Unit (EURCO) - 'XBB', # Bond Markets Unit European Monetary Unit (E.M.U.-6) - 'XBC', # Bond Markets Unit European Unit of Account 9 (E.U.A.-9) - 'XBD', # Bond Markets Unit European Unit of Account 17 (E.U.A.-17) - 'XXX', # no currency - 'XDR', # SDR (Special Drawing Right) + "XTS", # testing + "XAU", # gold + "XAG", # silver + "XPD", # palladium + "XPT", # platinum + "XBA", # Bond Markets Unit European Composite Unit (EURCO) + "XBB", # Bond Markets Unit European Monetary Unit (E.M.U.-6) + "XBC", # Bond Markets Unit European Unit of Account 9 (E.U.A.-9) + "XBD", # Bond Markets Unit European Unit of Account 17 (E.U.A.-17) + "XXX", # no currency + "XDR", # SDR (Special Drawing Right) } @@ -69,7 +69,7 @@ def _validate(cls, currency_code: str, _: core_schema.ValidationInfo) -> str: """ if currency_code not in cls.allowed_currencies: raise PydanticCustomError( - 'ISO4217', 'Invalid ISO 4217 currency code. See https://en.wikipedia.org/wiki/ISO_4217' + "ISO4217", "Invalid ISO 4217 currency code. See https://en.wikipedia.org/wiki/ISO_4217" ) return currency_code @@ -85,7 +85,7 @@ def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler ) -> dict[str, Any]: json_schema = handler(schema) - json_schema.update({'enum': cls.allowed_countries_list}) + json_schema.update({"enum": cls.allowed_countries_list}) return json_schema @@ -129,10 +129,10 @@ def _validate(cls, currency_symbol: str, _: core_schema.ValidationInfo) -> str: """ if currency_symbol not in cls.allowed_currencies: raise PydanticCustomError( - 'InvalidCurrency', - 'Invalid currency code.' - ' See https://en.wikipedia.org/wiki/ISO_4217. ' - 'Bonds, testing and precious metals codes are not allowed.', + "InvalidCurrency", + "Invalid currency code." + " See https://en.wikipedia.org/wiki/ISO_4217. " + "Bonds, testing and precious metals codes are not allowed.", ) return currency_symbol @@ -175,5 +175,5 @@ def __get_pydantic_json_schema__( """ json_schema = handler(schema) - json_schema.update({'enum': cls.allowed_countries_list}) + json_schema.update({"enum": cls.allowed_countries_list}) return json_schema diff --git a/pydantic_extra_types/isbn.py b/pydantic_extra_types/isbn.py index df573c68..1e6b606c 100644 --- a/pydantic_extra_types/isbn.py +++ b/pydantic_extra_types/isbn.py @@ -25,7 +25,7 @@ def isbn10_digit_calc(isbn: str) -> str: for check_digit in range(1, 11): if (total + check_digit) % 11 == 0: - valid_check_digit = 'X' if check_digit == 10 else str(check_digit) + valid_check_digit = "X" if check_digit == 10 else str(check_digit) return valid_check_digit @@ -115,23 +115,23 @@ def validate_isbn_format(value: str) -> None: isbn_length = len(value) if isbn_length not in (10, 13): - raise PydanticCustomError('isbn_length', f'Length for ISBN must be 10 or 13 digits, not {isbn_length}') + raise PydanticCustomError("isbn_length", f"Length for ISBN must be 10 or 13 digits, not {isbn_length}") if isbn_length == 10: - if not value[:-1].isdigit() or ((value[-1] != 'X') and (not value[-1].isdigit())): - raise PydanticCustomError('isbn10_invalid_characters', 'First 9 digits of ISBN-10 must be integers') + if not value[:-1].isdigit() or ((value[-1] != "X") and (not value[-1].isdigit())): + raise PydanticCustomError("isbn10_invalid_characters", "First 9 digits of ISBN-10 must be integers") if isbn10_digit_calc(value) != value[-1]: - raise PydanticCustomError('isbn_invalid_digit_check_isbn10', 'Provided digit is invalid for given ISBN') + raise PydanticCustomError("isbn_invalid_digit_check_isbn10", "Provided digit is invalid for given ISBN") if isbn_length == 13: if not value.isdigit(): - raise PydanticCustomError('isbn13_invalid_characters', 'All digits of ISBN-13 must be integers') - if value[:3] not in ('978', '979'): + raise PydanticCustomError("isbn13_invalid_characters", "All digits of ISBN-13 must be integers") + if value[:3] not in ("978", "979"): raise PydanticCustomError( - 'isbn_invalid_early_characters', 'The first 3 digits of ISBN-13 must be 978 or 979' + "isbn_invalid_early_characters", "The first 3 digits of ISBN-13 must be 978 or 979" ) if isbn13_digit_calc(value) != value[-1]: - raise PydanticCustomError('isbn_invalid_digit_check_isbn13', 'Provided digit is invalid for given ISBN') + raise PydanticCustomError("isbn_invalid_digit_check_isbn13", "Provided digit is invalid for given ISBN") @staticmethod def convert_isbn10_to_isbn13(value: str) -> str: @@ -145,8 +145,8 @@ def convert_isbn10_to_isbn13(value: str) -> str: """ if len(value) == 10: - base_isbn = f'978{value[:-1]}' + base_isbn = f"978{value[:-1]}" isbn13_digit = isbn13_digit_calc(base_isbn) - return ISBN(f'{base_isbn}{isbn13_digit}') + return ISBN(f"{base_isbn}{isbn13_digit}") return ISBN(value) diff --git a/pydantic_extra_types/language_code.py b/pydantic_extra_types/language_code.py index 117e8774..7bf24b13 100644 --- a/pydantic_extra_types/language_code.py +++ b/pydantic_extra_types/language_code.py @@ -55,7 +55,7 @@ def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ISO639_ """ if __input_value not in cls.allowed_values: raise PydanticCustomError( - 'ISO649_3', 'Invalid ISO 639-3 language code. See https://en.wikipedia.org/wiki/ISO_639-3' + "ISO649_3", "Invalid ISO 639-3 language code. See https://en.wikipedia.org/wiki/ISO_639-3" ) return cls(__input_value) @@ -95,7 +95,7 @@ def __get_pydantic_json_schema__( """ json_schema = handler(schema) - json_schema.update({'enum': cls.allowed_values_list}) + json_schema.update({"enum": cls.allowed_values_list}) return json_schema @@ -138,7 +138,7 @@ def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ISO639_ """ if __input_value not in cls.allowed_values: raise PydanticCustomError( - 'ISO649_5', 'Invalid ISO 639-5 language code. See https://en.wikipedia.org/wiki/ISO_639-5' + "ISO649_5", "Invalid ISO 639-5 language code. See https://en.wikipedia.org/wiki/ISO_639-5" ) return cls(__input_value) @@ -178,5 +178,5 @@ def __get_pydantic_json_schema__( """ json_schema = handler(schema) - json_schema.update({'enum': cls.allowed_values_list}) + json_schema.update({"enum": cls.allowed_values_list}) return json_schema diff --git a/pydantic_extra_types/mac_address.py b/pydantic_extra_types/mac_address.py index 9be15570..564cd040 100644 --- a/pydantic_extra_types/mac_address.py +++ b/pydantic_extra_types/mac_address.py @@ -70,22 +70,22 @@ def validate_mac_address(value: bytes) -> str: """ if len(value) < 14: raise PydanticCustomError( - 'mac_address_len', - 'Length for a {mac_address} MAC address must be {required_length}', - {'mac_address': value.decode(), 'required_length': 14}, + "mac_address_len", + "Length for a {mac_address} MAC address must be {required_length}", + {"mac_address": value.decode(), "required_length": 14}, ) - if value[2] in [ord(':'), ord('-')]: + if value[2] in [ord(":"), ord("-")]: if (len(value) + 1) % 3 != 0: raise PydanticCustomError( - 'mac_address_format', 'Must have the format xx:xx:xx:xx:xx:xx or xx-xx-xx-xx-xx-xx' + "mac_address_format", "Must have the format xx:xx:xx:xx:xx:xx or xx-xx-xx-xx-xx-xx" ) n = (len(value) + 1) // 3 if n not in (6, 8, 20): raise PydanticCustomError( - 'mac_address_format', - 'Length for a {mac_address} MAC address must be {required_length}', - {'mac_address': value.decode(), 'required_length': (6, 8, 20)}, + "mac_address_format", + "Length for a {mac_address} MAC address must be {required_length}", + {"mac_address": value.decode(), "required_length": (6, 8, 20)}, ) mac_address = bytearray(n) x = 0 @@ -95,17 +95,17 @@ def validate_mac_address(value: bytes) -> str: mac_address[i] = byte_value x += 3 except ValueError as e: - raise PydanticCustomError('mac_address_format', 'Unrecognized format') from e + raise PydanticCustomError("mac_address_format", "Unrecognized format") from e - elif value[4] == ord('.'): + elif value[4] == ord("."): if (len(value) + 1) % 5 != 0: - raise PydanticCustomError('mac_address_format', 'Must have the format xx.xx.xx.xx.xx.xx') + raise PydanticCustomError("mac_address_format", "Must have the format xx.xx.xx.xx.xx.xx") n = 2 * (len(value) + 1) // 5 if n not in (6, 8, 20): raise PydanticCustomError( - 'mac_address_format', - 'Length for a {mac_address} MAC address must be {required_length}', - {'mac_address': value.decode(), 'required_length': (6, 8, 20)}, + "mac_address_format", + "Length for a {mac_address} MAC address must be {required_length}", + {"mac_address": value.decode(), "required_length": (6, 8, 20)}, ) mac_address = bytearray(n) x = 0 @@ -117,9 +117,9 @@ def validate_mac_address(value: bytes) -> str: mac_address[i + 1] = byte_value x += 5 except ValueError as e: - raise PydanticCustomError('mac_address_format', 'Unrecognized format') from e + raise PydanticCustomError("mac_address_format", "Unrecognized format") from e else: - raise PydanticCustomError('mac_address_format', 'Unrecognized format') + raise PydanticCustomError("mac_address_format", "Unrecognized format") - return ':'.join(f'{b:02x}' for b in mac_address) + return ":".join(f"{b:02x}" for b in mac_address) diff --git a/pydantic_extra_types/payment.py b/pydantic_extra_types/payment.py index e3c040b6..d7e39c0f 100644 --- a/pydantic_extra_types/payment.py +++ b/pydantic_extra_types/payment.py @@ -15,18 +15,18 @@ class PaymentCardBrand(str, Enum): """Payment card brands supported by the [`PaymentCardNumber`][pydantic_extra_types.payment.PaymentCardNumber].""" - amex = 'American Express' - mastercard = 'Mastercard' - visa = 'Visa' - mir = 'Mir' - maestro = 'Maestro' - discover = 'Discover' - verve = 'Verve' - dankort = 'Dankort' - troy = 'Troy' - unionpay = 'UnionPay' - jcb = 'JCB' - other = 'other' + amex = "American Express" + mastercard = "Mastercard" + visa = "Visa" + mir = "Mir" + maestro = "Maestro" + discover = "Discover" + verve = "Verve" + dankort = "Dankort" + troy = "Troy" + unionpay = "UnionPay" + jcb = "JCB" + other = "other" def __str__(self) -> str: return self.value @@ -95,8 +95,8 @@ def validate_digits(cls, card_number: str) -> None: Raises: PydanticCustomError: If the card number is not all digits. """ - if not card_number or not all('0' <= c <= '9' for c in card_number): - raise PydanticCustomError('payment_card_number_digits', 'Card number is not all digits') + if not card_number or not all("0" <= c <= "9" for c in card_number): + raise PydanticCustomError("payment_card_number_digits", "Card number is not all digits") @classmethod def validate_luhn_check_digit(cls, card_number: str) -> str: @@ -124,7 +124,7 @@ def validate_luhn_check_digit(cls, card_number: str) -> str: sum_ += digit valid = sum_ % 10 == 0 if not valid: - raise PydanticCustomError('payment_card_number_luhn', 'Card number is not luhn valid') + raise PydanticCustomError("payment_card_number_luhn", "Card number is not luhn valid") return card_number @staticmethod @@ -144,27 +144,27 @@ def validate_brand(card_number: str) -> PaymentCardBrand: """ brand = PaymentCardBrand.other - if card_number[0] == '4': + if card_number[0] == "4": brand = PaymentCardBrand.visa required_length = [13, 16, 19] elif 51 <= int(card_number[:2]) <= 55: brand = PaymentCardBrand.mastercard required_length = [16] - elif card_number[:2] in {'34', '37'}: + elif card_number[:2] in {"34", "37"}: brand = PaymentCardBrand.amex required_length = [15] elif 2200 <= int(card_number[:4]) <= 2204: brand = PaymentCardBrand.mir required_length = list(range(16, 20)) - elif card_number[:4] in {'5018', '5020', '5038', '5893', '6304', '6759', '6761', '6762', '6763'} or card_number[ + elif card_number[:4] in {"5018", "5020", "5038", "5893", "6304", "6759", "6761", "6762", "6763"} or card_number[ :6 ] in ( - '676770', - '676774', + "676770", + "676774", ): brand = PaymentCardBrand.maestro required_length = list(range(12, 20)) - elif card_number.startswith('65') or 644 <= int(card_number[:3]) <= 649 or card_number.startswith('6011'): + elif card_number.startswith("65") or 644 <= int(card_number[:3]) <= 649 or card_number.startswith("6011"): brand = PaymentCardBrand.discover required_length = list(range(16, 20)) elif ( @@ -174,13 +174,13 @@ def validate_brand(card_number: str) -> PaymentCardBrand: ): brand = PaymentCardBrand.verve required_length = [16, 18, 19] - elif card_number[:4] in {'5019', '4571'}: + elif card_number[:4] in {"5019", "4571"}: brand = PaymentCardBrand.dankort required_length = [16] - elif card_number.startswith('9792'): + elif card_number.startswith("9792"): brand = PaymentCardBrand.troy required_length = [16] - elif card_number[:2] in {'62', '81'}: + elif card_number[:2] in {"62", "81"}: brand = PaymentCardBrand.unionpay required_length = [16, 19] elif 3528 <= int(card_number[:4]) <= 3589: @@ -191,9 +191,9 @@ def validate_brand(card_number: str) -> PaymentCardBrand: if not valid: raise PydanticCustomError( - 'payment_card_number_brand', + "payment_card_number_brand", f'Length for a {brand} card must be {" or ".join(map(str, required_length))}', - {'brand': brand, 'required_length': required_length}, + {"brand": brand, "required_length": required_length}, ) return brand diff --git a/pydantic_extra_types/pendulum_dt.py b/pydantic_extra_types/pendulum_dt.py index f507779d..395a2f46 100644 --- a/pydantic_extra_types/pendulum_dt.py +++ b/pydantic_extra_types/pendulum_dt.py @@ -70,5 +70,5 @@ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler try: data = parse(value) except Exception as exc: - raise PydanticCustomError('value_error', 'value is not a valid timestamp') from exc + raise PydanticCustomError("value_error", "value is not a valid timestamp") from exc return handler(data) diff --git a/pydantic_extra_types/phone_numbers.py b/pydantic_extra_types/phone_numbers.py index 7acaa898..135b39c4 100644 --- a/pydantic_extra_types/phone_numbers.py +++ b/pydantic_extra_types/phone_numbers.py @@ -34,7 +34,7 @@ class PhoneNumber(str): default_region_code: ClassVar[str | None] = None """The default region code to use when parsing phone numbers without an international prefix.""" - phone_format: str = 'RFC3966' + phone_format: str = "RFC3966" """The format of the phone number.""" min_length: int = 7 """The minimum length of the phone number.""" @@ -46,7 +46,7 @@ def __get_pydantic_json_schema__( cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler ) -> dict[str, Any]: json_schema = handler(schema) - json_schema.update({'format': 'phone'}) + json_schema.update({"format": "phone"}) return json_schema @classmethod @@ -61,8 +61,8 @@ def _validate(cls, phone_number: str, _: core_schema.ValidationInfo) -> str: try: parsed_number = phonenumbers.parse(phone_number, cls.default_region_code) except phonenumbers.phonenumberutil.NumberParseException as exc: - raise PydanticCustomError('value_error', 'value is not a valid phone number') from exc + raise PydanticCustomError("value_error", "value is not a valid phone number") from exc if not phonenumbers.is_valid_number(parsed_number): - raise PydanticCustomError('value_error', 'value is not a valid phone number') + raise PydanticCustomError("value_error", "value is not a valid phone number") return phonenumbers.format_number(parsed_number, getattr(phonenumbers.PhoneNumberFormat, cls.phone_format)) diff --git a/pydantic_extra_types/routing_number.py b/pydantic_extra_types/routing_number.py index 22ea6e88..022800ae 100644 --- a/pydantic_extra_types/routing_number.py +++ b/pydantic_extra_types/routing_number.py @@ -52,7 +52,7 @@ def __get_pydantic_core_schema__( ) @classmethod - def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> 'ABARoutingNumber': + def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> "ABARoutingNumber": return cls(__input_value) @classmethod @@ -66,7 +66,7 @@ def _validate_digits(cls, routing_number: str) -> None: PydanticCustomError: If the routing number is not all digits. """ if not routing_number.isdigit(): - raise PydanticCustomError('aba_routing_number', 'routing number is not all digits') + raise PydanticCustomError("aba_routing_number", "routing number is not all digits") @classmethod def _validate_routing_number(cls, routing_number: str) -> str: @@ -85,5 +85,5 @@ def _validate_routing_number(cls, routing_number: str) -> str: + sum(map(int, [routing_number[2], routing_number[5], routing_number[8]])) ) if checksum % 10 != 0: - raise PydanticCustomError('aba_routing_number', 'Incorrect ABA routing transit number') + raise PydanticCustomError("aba_routing_number", "Incorrect ABA routing transit number") return routing_number diff --git a/pydantic_extra_types/ulid.py b/pydantic_extra_types/ulid.py index d2bf650e..7c3d1f5f 100644 --- a/pydantic_extra_types/ulid.py +++ b/pydantic_extra_types/ulid.py @@ -58,5 +58,5 @@ def _validate_ulid(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHa else: ulid = _ULID.from_bytes(value) except ValueError: - raise PydanticCustomError('ulid_format', 'Unrecognized format') + raise PydanticCustomError("ulid_format", "Unrecognized format") return handler(ulid) diff --git a/pyproject.toml b/pyproject.toml index 8fc323d2..b3feffea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,18 @@ Documentation = 'https://docs.pydantic.dev/latest/' [tool.ruff] line-length = 120 -extend-select = ['Q', 'RUF100', 'C90', 'UP', 'I'] -isort = { known-first-party = ['pydantic_extra_types', 'tests'] } -flake8-quotes = {inline-quotes = 'single', multiline-quotes = 'double'} -mccabe = { max-complexity = 14 } + +[tool.ruff.lint] +select = ['Q', 'RUF100', 'C90', 'UP', 'I'] + +[tool.ruff.lint.mccabe] +max-complexity = 14 + +[tool.ruff.lint.isort] +known-third-party = ["pydantic_extra_types", "tests"] + +[tool.ruff.lint.pyupgrade] +keep-runtime-typing = true [tool.coverage.run] source = ['pydantic_extra_types'] @@ -89,11 +97,6 @@ exclude_lines = [ '@overload', ] -[tool.black] -color = true -line-length = 120 -target-version = ['py37'] -skip-string-normalization = true [tool.mypy] strict = true diff --git a/requirements/linting.in b/requirements/linting.in index f007182d..06a5fced 100644 --- a/requirements/linting.in +++ b/requirements/linting.in @@ -1,6 +1,4 @@ pre-commit mypy annotated-types -black -pyupgrade ruff diff --git a/requirements/linting.txt b/requirements/linting.txt index d4367dab..9bc7bc16 100644 --- a/requirements/linting.txt +++ b/requirements/linting.txt @@ -6,47 +6,31 @@ # annotated-types==0.6.0 # via -r requirements/linting.in -black==23.12.1 - # via -r requirements/linting.in cfgv==3.4.0 # via pre-commit -click==8.1.7 - # via black distlib==0.3.8 # via virtualenv filelock==3.13.1 # via virtualenv -identify==2.5.33 +identify==2.5.35 # via pre-commit mypy==1.8.0 # via -r requirements/linting.in mypy-extensions==1.0.0 - # via - # black - # mypy + # via mypy nodeenv==1.8.0 # via pre-commit -packaging==23.2 - # via black -pathspec==0.12.1 - # via black -platformdirs==4.1.0 - # via - # black - # virtualenv -pre-commit==3.6.0 - # via -r requirements/linting.in -pyupgrade==3.15.0 +platformdirs==4.2.0 + # via virtualenv +pre-commit==3.6.2 # via -r requirements/linting.in pyyaml==6.0.1 # via pre-commit -ruff==0.1.14 +ruff==0.2.2 # via -r requirements/linting.in -tokenize-rt==5.2.0 - # via pyupgrade -typing-extensions==4.9.0 +typing-extensions==4.10.0 # via mypy -virtualenv==20.25.0 +virtualenv==20.25.1 # via pre-commit # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/pyproject.txt b/requirements/pyproject.txt index 28e624be..5b774721 100644 --- a/requirements/pyproject.txt +++ b/requirements/pyproject.txt @@ -8,13 +8,13 @@ annotated-types==0.6.0 # via pydantic pendulum==3.0.0 # via pydantic-extra-types (pyproject.toml) -phonenumbers==8.13.28 +phonenumbers==8.13.31 # via pydantic-extra-types (pyproject.toml) pycountry==23.12.11 # via pydantic-extra-types (pyproject.toml) -pydantic==2.5.3 +pydantic==2.6.3 # via pydantic-extra-types (pyproject.toml) -pydantic-core==2.14.6 +pydantic-core==2.16.3 # via pydantic python-dateutil==2.8.2 # via @@ -26,9 +26,9 @@ six==1.16.0 # via python-dateutil time-machine==2.13.0 # via pendulum -typing-extensions==4.9.0 +typing-extensions==4.10.0 # via # pydantic # pydantic-core -tzdata==2023.4 +tzdata==2024.1 # via pendulum diff --git a/requirements/testing.txt b/requirements/testing.txt index 07387d57..0c36fc59 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -4,13 +4,13 @@ # # pip-compile --no-emit-index-url --output-file=requirements/testing.txt requirements/testing.in # -certifi==2023.11.17 +certifi==2024.2.2 # via requests charset-normalizer==3.3.2 # via requests codecov==2.1.13 # via -r requirements/testing.in -coverage[toml]==7.4.0 +coverage[toml]==7.4.3 # via # -r requirements/testing.in # codecov @@ -31,7 +31,7 @@ pluggy==1.4.0 # via pytest pygments==2.17.2 # via rich -pytest==7.4.4 +pytest==8.0.2 # via # -r requirements/testing.in # pytest-cov @@ -40,11 +40,11 @@ pytest-cov==4.1.0 # via -r requirements/testing.in pytest-pretty==1.2.0 # via -r requirements/testing.in -pytz==2023.3.post1 +pytz==2024.1 # via dirty-equals requests==2.31.0 # via codecov rich==13.7.0 # via pytest-pretty -urllib3==2.1.0 +urllib3==2.2.1 # via requests diff --git a/tests/test_coordinate.py b/tests/test_coordinate.py index 199d988e..f123b2c8 100644 --- a/tests/test_coordinate.py +++ b/tests/test_coordinate.py @@ -4,7 +4,6 @@ import pytest from pydantic import BaseModel, ValidationError from pydantic_core._pydantic_core import ArgsKwargs - from pydantic_extra_types.coordinate import Coordinate, Latitude, Longitude @@ -21,38 +20,38 @@ class Lng(BaseModel): @pytest.mark.parametrize( - 'coord, result, error', + "coord, result, error", [ # Valid coordinates ((20.0, 10.0), (20.0, 10.0), None), ((-90.0, 0.0), (-90.0, 0.0), None), - (('20.0', 10.0), (20.0, 10.0), None), - ((20.0, '10.0'), (20.0, 10.0), None), + (("20.0", 10.0), (20.0, 10.0), None), + ((20.0, "10.0"), (20.0, 10.0), None), ((45.678, -123.456), (45.678, -123.456), None), - (('45.678, -123.456'), (45.678, -123.456), None), + (("45.678, -123.456"), (45.678, -123.456), None), (Coordinate(20.0, 10.0), (20.0, 10.0), None), (Coordinate(latitude=0, longitude=0), (0, 0), None), (ArgsKwargs(args=()), (0, 0), None), (ArgsKwargs(args=(1, 0.0)), (1.0, 0), None), # # Invalid coordinates - ((), None, 'Field required'), # Empty tuple - ((10.0,), None, 'Field required'), # Tuple with only one value - (('ten, '), None, 'string is not recognized as a valid coordinate'), - ((20.0, 10.0, 30.0), None, 'Tuple should have at most 2 items'), # Tuple with more than 2 values - (ArgsKwargs(args=(1.0,)), None, 'Input should be a dictionary or an instance of Coordinate'), + ((), None, "Field required"), # Empty tuple + ((10.0,), None, "Field required"), # Tuple with only one value + (("ten, "), None, "string is not recognized as a valid coordinate"), + ((20.0, 10.0, 30.0), None, "Tuple should have at most 2 items"), # Tuple with more than 2 values + (ArgsKwargs(args=(1.0,)), None, "Input should be a dictionary or an instance of Coordinate"), ( - '20.0, 10.0, 30.0', + "20.0, 10.0, 30.0", None, - 'Input should be a dictionary or an instance of Coordinate ', + "Input should be a dictionary or an instance of Coordinate ", ), # Str with more than 2 values - ('20.0, 10.0, 30.0', None, 'Unexpected positional argument'), # Str with more than 2 values - (2, None, 'Input should be a dictionary or an instance of Coordinate'), # Wrong type + ("20.0, 10.0, 30.0", None, "Unexpected positional argument"), # Str with more than 2 values + (2, None, "Input should be a dictionary or an instance of Coordinate"), # Wrong type ], ) def test_format_for_coordinate(coord: (Any, Any), result: (float, float), error: Optional[Pattern]): if error is None: _coord: Coordinate = Coord(coord=coord).coord - print('vars(_coord)', vars(_coord)) + print("vars(_coord)", vars(_coord)) assert _coord.latitude == result[0] assert _coord.longitude == result[1] else: @@ -61,14 +60,14 @@ def test_format_for_coordinate(coord: (Any, Any), result: (float, float), error: @pytest.mark.parametrize( - 'coord, error', + "coord, error", [ # Valid coordinates ((-90.0, 0.0), None), ((50.0, 180.0), None), # Invalid coordinates - ((-91.0, 0.0), 'Input should be greater than or equal to -90'), - ((50.0, 181.0), 'Input should be less than or equal to 180'), + ((-91.0, 0.0), "Input should be greater than or equal to -90"), + ((50.0, 181.0), "Input should be less than or equal to 180"), ], ) def test_limit_for_coordinate(coord: (Any, Any), error: Optional[Pattern]): @@ -82,15 +81,15 @@ def test_limit_for_coordinate(coord: (Any, Any), error: Optional[Pattern]): @pytest.mark.parametrize( - 'latitude, valid', + "latitude, valid", [ # Valid latitude (20.0, True), (3.0000000000000000000000, True), (90.0, True), - ('90.0', True), + ("90.0", True), (-90.0, True), - ('-90.0', True), + ("-90.0", True), # Unvalid latitude (91.0, False), (-91.0, False), @@ -101,20 +100,20 @@ def test_format_latitude(latitude: float, valid: bool): _lat = Lat(lat=latitude).lat assert _lat == float(latitude) else: - with pytest.raises(ValidationError, match='1 validation error for Lat'): + with pytest.raises(ValidationError, match="1 validation error for Lat"): Lat(lat=latitude) @pytest.mark.parametrize( - 'longitude, valid', + "longitude, valid", [ # Valid latitude (20.0, True), (3.0000000000000000000000, True), (90.0, True), - ('90.0', True), + ("90.0", True), (-90.0, True), - ('-90.0', True), + ("-90.0", True), (91.0, True), (-91.0, True), (180.0, True), @@ -129,21 +128,21 @@ def test_format_longitude(longitude: float, valid: bool): _lng = Lng(lng=longitude).lng assert _lng == float(longitude) else: - with pytest.raises(ValidationError, match='1 validation error for Lng'): + with pytest.raises(ValidationError, match="1 validation error for Lng"): Lng(lng=longitude) def test_str_repr(): - assert str(Coord(coord=(20.0, 10.0)).coord) == '20.0,10.0' - assert str(Coord(coord=('20.0, 10.0')).coord) == '20.0,10.0' - assert repr(Coord(coord=(20.0, 10.0)).coord) == 'Coordinate(latitude=20.0, longitude=10.0)' + assert str(Coord(coord=(20.0, 10.0)).coord) == "20.0,10.0" + assert str(Coord(coord=("20.0, 10.0")).coord) == "20.0,10.0" + assert repr(Coord(coord=(20.0, 10.0)).coord) == "Coordinate(latitude=20.0, longitude=10.0)" def test_eq(): - assert Coord(coord=(20.0, 10.0)).coord != Coord(coord='20.0,11.0').coord - assert Coord(coord=('20.0, 10.0')).coord != Coord(coord='20.0,11.0').coord - assert Coord(coord=('20.0, 10.0')).coord != Coord(coord='20.0,11.0').coord - assert Coord(coord=(20.0, 10.0)).coord == Coord(coord='20.0,10.0').coord + assert Coord(coord=(20.0, 10.0)).coord != Coord(coord="20.0,11.0").coord + assert Coord(coord=("20.0, 10.0")).coord != Coord(coord="20.0,11.0").coord + assert Coord(coord=("20.0, 10.0")).coord != Coord(coord="20.0,11.0").coord + assert Coord(coord=(20.0, 10.0)).coord == Coord(coord="20.0,10.0").coord def test_hashable(): @@ -155,42 +154,42 @@ def test_json_schema(): class Model(BaseModel): value: Coordinate - assert Model.model_json_schema(mode='validation')['$defs']['Coordinate'] == { - 'properties': { - 'latitude': {'maximum': 90.0, 'minimum': -90.0, 'title': 'Latitude', 'type': 'number'}, - 'longitude': {'maximum': 180.0, 'minimum': -180.0, 'title': 'Longitude', 'type': 'number'}, + assert Model.model_json_schema(mode="validation")["$defs"]["Coordinate"] == { + "properties": { + "latitude": {"maximum": 90.0, "minimum": -90.0, "title": "Latitude", "type": "number"}, + "longitude": {"maximum": 180.0, "minimum": -180.0, "title": "Longitude", "type": "number"}, }, - 'required': ['latitude', 'longitude'], - 'title': 'Coordinate', - 'type': 'object', + "required": ["latitude", "longitude"], + "title": "Coordinate", + "type": "object", } - assert Model.model_json_schema(mode='validation')['properties']['value'] == { - 'anyOf': [ - {'$ref': '#/$defs/Coordinate'}, + assert Model.model_json_schema(mode="validation")["properties"]["value"] == { + "anyOf": [ + {"$ref": "#/$defs/Coordinate"}, { - 'maxItems': 2, - 'minItems': 2, - 'prefixItems': [{'type': 'number'}, {'type': 'number'}], - 'type': 'array', + "maxItems": 2, + "minItems": 2, + "prefixItems": [{"type": "number"}, {"type": "number"}], + "type": "array", }, - {'type': 'string'}, + {"type": "string"}, ], - 'title': 'Value', + "title": "Value", } - assert Model.model_json_schema(mode='serialization') == { - '$defs': { - 'Coordinate': { - 'properties': { - 'latitude': {'maximum': 90.0, 'minimum': -90.0, 'title': 'Latitude', 'type': 'number'}, - 'longitude': {'maximum': 180.0, 'minimum': -180.0, 'title': 'Longitude', 'type': 'number'}, + assert Model.model_json_schema(mode="serialization") == { + "$defs": { + "Coordinate": { + "properties": { + "latitude": {"maximum": 90.0, "minimum": -90.0, "title": "Latitude", "type": "number"}, + "longitude": {"maximum": 180.0, "minimum": -180.0, "title": "Longitude", "type": "number"}, }, - 'required': ['latitude', 'longitude'], - 'title': 'Coordinate', - 'type': 'object', + "required": ["latitude", "longitude"], + "title": "Coordinate", + "type": "object", } }, - 'properties': {'value': {'allOf': [{'$ref': '#/$defs/Coordinate'}], 'title': 'Value'}}, - 'required': ['value'], - 'title': 'Model', - 'type': 'object', + "properties": {"value": {"allOf": [{"$ref": "#/$defs/Coordinate"}], "title": "Value"}}, + "required": ["value"], + "title": "Model", + "type": "object", } diff --git a/tests/test_country_code.py b/tests/test_country_code.py index 13bb85a0..39c27d60 100644 --- a/tests/test_country_code.py +++ b/tests/test_country_code.py @@ -2,7 +2,6 @@ import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.country import ( CountryAlpha2, CountryAlpha3, @@ -18,7 +17,7 @@ PARAMS_AMOUNT = 20 -@pytest.fixture(scope='module', name='ProductAlpha2') +@pytest.fixture(scope="module", name="ProductAlpha2") def product_alpha2_fixture(): class Product(BaseModel): made_in: CountryAlpha2 @@ -26,7 +25,7 @@ class Product(BaseModel): return Product -@pytest.fixture(scope='module', name='ProductAlpha3') +@pytest.fixture(scope="module", name="ProductAlpha3") def product_alpha3_fixture(): class Product(BaseModel): made_in: CountryAlpha3 @@ -34,7 +33,7 @@ class Product(BaseModel): return Product -@pytest.fixture(scope='module', name='ProductShortName') +@pytest.fixture(scope="module", name="ProductShortName") def product_short_name_fixture(): class Product(BaseModel): made_in: CountryShortName @@ -42,7 +41,7 @@ class Product(BaseModel): return Product -@pytest.fixture(scope='module', name='ProductNumericCode') +@pytest.fixture(scope="module", name="ProductNumericCode") def product_numeric_code_fixture(): class Product(BaseModel): made_in: CountryNumericCode @@ -50,7 +49,7 @@ class Product(BaseModel): return Product -@pytest.mark.parametrize('alpha2, country_data', list(_index_by_alpha2().items())[:PARAMS_AMOUNT]) +@pytest.mark.parametrize("alpha2, country_data", list(_index_by_alpha2().items())[:PARAMS_AMOUNT]) def test_valid_alpha2(alpha2: str, country_data: CountryInfo, ProductAlpha2): banana = ProductAlpha2(made_in=alpha2) assert banana.made_in == country_data.alpha2 @@ -59,13 +58,13 @@ def test_valid_alpha2(alpha2: str, country_data: CountryInfo, ProductAlpha2): assert banana.made_in.short_name == country_data.short_name -@pytest.mark.parametrize('alpha2', list(printable)) +@pytest.mark.parametrize("alpha2", list(printable)) def test_invalid_alpha2(alpha2: str, ProductAlpha2): - with pytest.raises(ValidationError, match='Invalid country alpha2 code'): + with pytest.raises(ValidationError, match="Invalid country alpha2 code"): ProductAlpha2(made_in=alpha2) -@pytest.mark.parametrize('alpha3, country_data', list(_index_by_alpha3().items())[:PARAMS_AMOUNT]) +@pytest.mark.parametrize("alpha3, country_data", list(_index_by_alpha3().items())[:PARAMS_AMOUNT]) def test_valid_alpha3(alpha3: str, country_data: CountryInfo, ProductAlpha3): banana = ProductAlpha3(made_in=alpha3) assert banana.made_in == country_data.alpha3 @@ -74,13 +73,13 @@ def test_valid_alpha3(alpha3: str, country_data: CountryInfo, ProductAlpha3): assert banana.made_in.short_name == country_data.short_name -@pytest.mark.parametrize('alpha3', list(printable)) +@pytest.mark.parametrize("alpha3", list(printable)) def test_invalid_alpha3(alpha3: str, ProductAlpha3): - with pytest.raises(ValidationError, match='Invalid country alpha3 code'): + with pytest.raises(ValidationError, match="Invalid country alpha3 code"): ProductAlpha3(made_in=alpha3) -@pytest.mark.parametrize('short_name, country_data', list(_index_by_short_name().items())[:PARAMS_AMOUNT]) +@pytest.mark.parametrize("short_name, country_data", list(_index_by_short_name().items())[:PARAMS_AMOUNT]) def test_valid_short_name(short_name: str, country_data: CountryInfo, ProductShortName): banana = ProductShortName(made_in=short_name) assert banana.made_in == country_data.short_name @@ -89,13 +88,13 @@ def test_valid_short_name(short_name: str, country_data: CountryInfo, ProductSho assert banana.made_in.numeric_code == country_data.numeric_code -@pytest.mark.parametrize('short_name', list(printable)) +@pytest.mark.parametrize("short_name", list(printable)) def test_invalid_short_name(short_name: str, ProductShortName): - with pytest.raises(ValidationError, match='Invalid country short name'): + with pytest.raises(ValidationError, match="Invalid country short name"): ProductShortName(made_in=short_name) -@pytest.mark.parametrize('numeric_code, country_data', list(_index_by_numeric_code().items())[:PARAMS_AMOUNT]) +@pytest.mark.parametrize("numeric_code, country_data", list(_index_by_numeric_code().items())[:PARAMS_AMOUNT]) def test_valid_numeric_code(numeric_code: str, country_data: CountryInfo, ProductNumericCode): banana = ProductNumericCode(made_in=numeric_code) assert banana.made_in == country_data.numeric_code @@ -104,7 +103,7 @@ def test_valid_numeric_code(numeric_code: str, country_data: CountryInfo, Produc assert banana.made_in.short_name == country_data.short_name -@pytest.mark.parametrize('numeric_code', list(printable)) +@pytest.mark.parametrize("numeric_code", list(printable)) def test_invalid_numeric_code(numeric_code: str, ProductNumericCode): - with pytest.raises(ValidationError, match='Invalid country numeric code'): + with pytest.raises(ValidationError, match="Invalid country numeric code"): ProductNumericCode(made_in=numeric_code) diff --git a/tests/test_currency_code.py b/tests/test_currency_code.py index d259a8d4..55a5e4ca 100644 --- a/tests/test_currency_code.py +++ b/tests/test_currency_code.py @@ -3,7 +3,6 @@ import pycountry import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types import currency_code @@ -18,15 +17,15 @@ class CurrencyCheckingModel(BaseModel): forbidden_currencies = sorted(currency_code._CODES_FOR_BONDS_METAL_TESTING) -@pytest.mark.parametrize('currency', map(lambda code: code.alpha_3, pycountry.currencies)) +@pytest.mark.parametrize("currency", map(lambda code: code.alpha_3, pycountry.currencies)) def test_ISO4217_code_ok(currency: str): model = ISO4217CheckingModel(currency=currency) assert model.currency == currency - assert model.model_dump() == {'currency': currency} # test serialization + assert model.model_dump() == {"currency": currency} # test serialization @pytest.mark.parametrize( - 'currency', + "currency", filter( lambda code: code not in currency_code._CODES_FOR_BONDS_METAL_TESTING, map(lambda code: code.alpha_3, pycountry.currencies), @@ -35,29 +34,29 @@ def test_ISO4217_code_ok(currency: str): def test_everyday_code_ok(currency: str): model = CurrencyCheckingModel(currency=currency) assert model.currency == currency - assert model.model_dump() == {'currency': currency} # test serialization + assert model.model_dump() == {"currency": currency} # test serialization def test_ISO4217_fails(): with pytest.raises( ValidationError, match=re.escape( - '1 validation error for ISO4217CheckingModel\ncurrency\n ' - 'Invalid ISO 4217 currency code. See https://en.wikipedia.org/wiki/ISO_4217 ' + "1 validation error for ISO4217CheckingModel\ncurrency\n " + "Invalid ISO 4217 currency code. See https://en.wikipedia.org/wiki/ISO_4217 " "[type=ISO4217, input_value='OMG', input_type=str]" ), ): - ISO4217CheckingModel(currency='OMG') + ISO4217CheckingModel(currency="OMG") -@pytest.mark.parametrize('forbidden_currency', forbidden_currencies) +@pytest.mark.parametrize("forbidden_currency", forbidden_currencies) def test_forbidden_everyday(forbidden_currency): with pytest.raises( ValidationError, match=re.escape( - '1 validation error for CurrencyCheckingModel\ncurrency\n ' - 'Invalid currency code. See https://en.wikipedia.org/wiki/ISO_4217. ' - 'Bonds, testing and precious metals codes are not allowed. ' + "1 validation error for CurrencyCheckingModel\ncurrency\n " + "Invalid currency code. See https://en.wikipedia.org/wiki/ISO_4217. " + "Bonds, testing and precious metals codes are not allowed. " f"[type=InvalidCurrency, input_value='{forbidden_currency}', input_type=str]" ), ): diff --git a/tests/test_isbn.py b/tests/test_isbn.py index e44677ba..c4bb5fff 100644 --- a/tests/test_isbn.py +++ b/tests/test_isbn.py @@ -2,7 +2,6 @@ import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.isbn import ISBN @@ -12,143 +11,143 @@ class Book(BaseModel): isbn_length_test_cases = [ # Valid ISBNs - ('8537809667', '9788537809662', True), # ISBN-10 as input - ('9788537809662', '9788537809662', True), # ISBN-13 as input - ('080442957X', '9780804429573', True), # ISBN-10 ending in "X" as input - ('9788584390670', '9788584390670', True), # ISBN-13 Starting with 978 - ('9790306406156', '9790306406156', True), # ISBN-13 starting with 979 + ("8537809667", "9788537809662", True), # ISBN-10 as input + ("9788537809662", "9788537809662", True), # ISBN-13 as input + ("080442957X", "9780804429573", True), # ISBN-10 ending in "X" as input + ("9788584390670", "9788584390670", True), # ISBN-13 Starting with 978 + ("9790306406156", "9790306406156", True), # ISBN-13 starting with 979 # Invalid ISBNs - ('97885843906701', None, False), # Length: 14 (Higher) - ('978858439067', None, False), # Length: 12 (In Between) - ('97885843906', None, False), # Length: 11 (In Between) - ('978858439', None, False), # Length: 9 (Lower) - ('', None, False), # Length: 0 (Lower) + ("97885843906701", None, False), # Length: 14 (Higher) + ("978858439067", None, False), # Length: 12 (In Between) + ("97885843906", None, False), # Length: 11 (In Between) + ("978858439", None, False), # Length: 9 (Lower) + ("", None, False), # Length: 0 (Lower) ] -@pytest.mark.parametrize('input_isbn, output_isbn, valid', isbn_length_test_cases) +@pytest.mark.parametrize("input_isbn, output_isbn, valid", isbn_length_test_cases) def test_isbn_length(input_isbn: Any, output_isbn: str, valid: bool) -> None: if valid: assert Book(isbn=ISBN(input_isbn)).isbn == output_isbn else: - with pytest.raises(ValidationError, match='isbn_length'): + with pytest.raises(ValidationError, match="isbn_length"): Book(isbn=ISBN(input_isbn)) isbn10_digits_test_cases = [ # Valid ISBNs - ('8537809667', '9788537809662', True), # ISBN-10 as input - ('080442957X', '9780804429573', True), # ISBN-10 ending in "X" as input + ("8537809667", "9788537809662", True), # ISBN-10 as input + ("080442957X", "9780804429573", True), # ISBN-10 ending in "X" as input # Invalid ISBNs - ('@80442957X', None, False), # Non Integer in [0] position - ('8@37809667', None, False), # Non Integer in [1] position - ('85@7809667', None, False), # Non Integer in [2] position - ('853@809667', None, False), # Non Integer in [3] position - ('8537@09667', None, False), # Non Integer in [4] position - ('85378@9667', None, False), # Non Integer in [5] position - ('853780@667', None, False), # Non Integer in [6] position - ('8537809@67', None, False), # Non Integer in [7] position - ('85378096@7', None, False), # Non Integer in [8] position - ('853780966@', None, False), # Non Integer or X in [9] position + ("@80442957X", None, False), # Non Integer in [0] position + ("8@37809667", None, False), # Non Integer in [1] position + ("85@7809667", None, False), # Non Integer in [2] position + ("853@809667", None, False), # Non Integer in [3] position + ("8537@09667", None, False), # Non Integer in [4] position + ("85378@9667", None, False), # Non Integer in [5] position + ("853780@667", None, False), # Non Integer in [6] position + ("8537809@67", None, False), # Non Integer in [7] position + ("85378096@7", None, False), # Non Integer in [8] position + ("853780966@", None, False), # Non Integer or X in [9] position ] -@pytest.mark.parametrize('input_isbn, output_isbn, valid', isbn10_digits_test_cases) +@pytest.mark.parametrize("input_isbn, output_isbn, valid", isbn10_digits_test_cases) def test_isbn10_digits(input_isbn: Any, output_isbn: str, valid: bool) -> None: if valid: assert Book(isbn=ISBN(input_isbn)).isbn == output_isbn else: - with pytest.raises(ValidationError, match='isbn10_invalid_characters'): + with pytest.raises(ValidationError, match="isbn10_invalid_characters"): Book(isbn=ISBN(input_isbn)) isbn13_digits_test_cases = [ # Valid ISBNs - ('9788537809662', '9788537809662', True), # ISBN-13 as input - ('9780306406157', '9780306406157', True), # ISBN-13 as input - ('9788584390670', '9788584390670', True), # ISBN-13 Starting with 978 - ('9790306406156', '9790306406156', True), # ISBN-13 starting with 979 + ("9788537809662", "9788537809662", True), # ISBN-13 as input + ("9780306406157", "9780306406157", True), # ISBN-13 as input + ("9788584390670", "9788584390670", True), # ISBN-13 Starting with 978 + ("9790306406156", "9790306406156", True), # ISBN-13 starting with 979 # Invalid ISBNs - ('@788537809662', None, False), # Non Integer in [0] position - ('9@88537809662', None, False), # Non Integer in [1] position - ('97@8537809662', None, False), # Non Integer in [2] position - ('978@537809662', None, False), # Non Integer in [3] position - ('9788@37809662', None, False), # Non Integer in [4] position - ('97885@7809662', None, False), # Non Integer in [5] position - ('978853@809662', None, False), # Non Integer in [6] position - ('9788537@09662', None, False), # Non Integer in [7] position - ('97885378@9662', None, False), # Non Integer in [8] position - ('978853780@662', None, False), # Non Integer in [9] position - ('9788537809@62', None, False), # Non Integer in [10] position - ('97885378096@2', None, False), # Non Integer in [11] position - ('978853780966@', None, False), # Non Integer in [12] position + ("@788537809662", None, False), # Non Integer in [0] position + ("9@88537809662", None, False), # Non Integer in [1] position + ("97@8537809662", None, False), # Non Integer in [2] position + ("978@537809662", None, False), # Non Integer in [3] position + ("9788@37809662", None, False), # Non Integer in [4] position + ("97885@7809662", None, False), # Non Integer in [5] position + ("978853@809662", None, False), # Non Integer in [6] position + ("9788537@09662", None, False), # Non Integer in [7] position + ("97885378@9662", None, False), # Non Integer in [8] position + ("978853780@662", None, False), # Non Integer in [9] position + ("9788537809@62", None, False), # Non Integer in [10] position + ("97885378096@2", None, False), # Non Integer in [11] position + ("978853780966@", None, False), # Non Integer in [12] position ] -@pytest.mark.parametrize('input_isbn, output_isbn, valid', isbn13_digits_test_cases) +@pytest.mark.parametrize("input_isbn, output_isbn, valid", isbn13_digits_test_cases) def test_isbn13_digits(input_isbn: Any, output_isbn: str, valid: bool) -> None: if valid: assert Book(isbn=ISBN(input_isbn)).isbn == output_isbn else: - with pytest.raises(ValidationError, match='isbn13_invalid_characters'): + with pytest.raises(ValidationError, match="isbn13_invalid_characters"): Book(isbn=ISBN(input_isbn)) isbn13_early_digits_test_cases = [ # Valid ISBNs - ('9780306406157', '9780306406157', True), # ISBN-13 as input - ('9788584390670', '9788584390670', True), # ISBN-13 Starting with 978 - ('9790306406156', '9790306406156', True), # ISBN-13 starting with 979 + ("9780306406157", "9780306406157", True), # ISBN-13 as input + ("9788584390670", "9788584390670", True), # ISBN-13 Starting with 978 + ("9790306406156", "9790306406156", True), # ISBN-13 starting with 979 # Invalid ISBNs - ('1788584390670', None, False), # Does not start with 978 or 979 - ('9288584390670', None, False), # Does not start with 978 or 979 - ('9738584390670', None, False), # Does not start with 978 or 979 + ("1788584390670", None, False), # Does not start with 978 or 979 + ("9288584390670", None, False), # Does not start with 978 or 979 + ("9738584390670", None, False), # Does not start with 978 or 979 ] -@pytest.mark.parametrize('input_isbn, output_isbn, valid', isbn13_early_digits_test_cases) +@pytest.mark.parametrize("input_isbn, output_isbn, valid", isbn13_early_digits_test_cases) def test_isbn13_early_digits(input_isbn: Any, output_isbn: str, valid: bool) -> None: if valid: assert Book(isbn=ISBN(input_isbn)).isbn == output_isbn else: - with pytest.raises(ValidationError, match='isbn_invalid_early_characters'): + with pytest.raises(ValidationError, match="isbn_invalid_early_characters"): Book(isbn=ISBN(input_isbn)) isbn_last_digit_test_cases = [ # Valid ISBNs - ('8537809667', '9788537809662', True), # ISBN-10 as input - ('9788537809662', '9788537809662', True), # ISBN-13 as input - ('080442957X', '9780804429573', True), # ISBN-10 ending in "X" as input - ('9788584390670', '9788584390670', True), # ISBN-13 Starting with 978 - ('9790306406156', '9790306406156', True), # ISBN-13 starting with 979 + ("8537809667", "9788537809662", True), # ISBN-10 as input + ("9788537809662", "9788537809662", True), # ISBN-13 as input + ("080442957X", "9780804429573", True), # ISBN-10 ending in "X" as input + ("9788584390670", "9788584390670", True), # ISBN-13 Starting with 978 + ("9790306406156", "9790306406156", True), # ISBN-13 starting with 979 # Invalid ISBNs - ('8537809663', None, False), # ISBN-10 as input with wrong last digit - ('9788537809661', None, False), # ISBN-13 as input with wrong last digit - ('080442953X', None, False), # ISBN-10 ending in "X" as input with wrong last digit - ('9788584390671', None, False), # ISBN-13 Starting with 978 with wrong last digit - ('9790306406155', None, False), # ISBN-13 starting with 979 with wrong last digit + ("8537809663", None, False), # ISBN-10 as input with wrong last digit + ("9788537809661", None, False), # ISBN-13 as input with wrong last digit + ("080442953X", None, False), # ISBN-10 ending in "X" as input with wrong last digit + ("9788584390671", None, False), # ISBN-13 Starting with 978 with wrong last digit + ("9790306406155", None, False), # ISBN-13 starting with 979 with wrong last digit ] -@pytest.mark.parametrize('input_isbn, output_isbn, valid', isbn_last_digit_test_cases) +@pytest.mark.parametrize("input_isbn, output_isbn, valid", isbn_last_digit_test_cases) def test_isbn_last_digit(input_isbn: Any, output_isbn: str, valid: bool) -> None: if valid: assert Book(isbn=ISBN(input_isbn)).isbn == output_isbn else: - with pytest.raises(ValidationError, match='isbn_invalid_digit_check_isbn'): + with pytest.raises(ValidationError, match="isbn_invalid_digit_check_isbn"): Book(isbn=ISBN(input_isbn)) isbn_conversion_test_cases = [ # Valid ISBNs - ('8537809667', '9788537809662'), - ('080442957X', '9780804429573'), - ('9788584390670', '9788584390670'), - ('9790306406156', '9790306406156'), + ("8537809667", "9788537809662"), + ("080442957X", "9780804429573"), + ("9788584390670", "9788584390670"), + ("9790306406156", "9790306406156"), ] -@pytest.mark.parametrize('input_isbn, output_isbn', isbn_conversion_test_cases) +@pytest.mark.parametrize("input_isbn, output_isbn", isbn_conversion_test_cases) def test_isbn_conversion(input_isbn: Any, output_isbn: str) -> None: assert Book(isbn=ISBN(input_isbn)).isbn == output_isbn diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py index 5daa2460..2ce5ca92 100644 --- a/tests/test_json_schema.py +++ b/tests/test_json_schema.py @@ -1,8 +1,7 @@ import pycountry +import pydantic_extra_types import pytest from pydantic import BaseModel - -import pydantic_extra_types from pydantic_extra_types.color import Color from pydantic_extra_types.coordinate import Coordinate, Latitude, Longitude from pydantic_extra_types.country import ( @@ -36,255 +35,255 @@ @pytest.mark.parametrize( - 'cls,expected', + "cls,expected", [ ( Color, { - 'properties': {'x': {'format': 'color', 'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "properties": {"x": {"format": "color", "title": "X", "type": "string"}}, + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( PaymentCardNumber, { - 'properties': { - 'x': { - 'maxLength': 19, - 'minLength': 12, - 'title': 'X', - 'type': 'string', + "properties": { + "x": { + "maxLength": 19, + "minLength": 12, + "title": "X", + "type": "string", } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( CountryAlpha2, { - 'properties': {'x': {'pattern': '^\\w{2}$', 'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "properties": {"x": {"pattern": "^\\w{2}$", "title": "X", "type": "string"}}, + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( CountryAlpha3, { - 'properties': {'x': {'pattern': '^\\w{3}$', 'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "properties": {"x": {"pattern": "^\\w{3}$", "title": "X", "type": "string"}}, + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( CountryNumericCode, { - 'properties': {'x': {'pattern': '^[0-9]{3}$', 'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "properties": {"x": {"pattern": "^[0-9]{3}$", "title": "X", "type": "string"}}, + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( CountryShortName, { - 'properties': {'x': {'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "properties": {"x": {"title": "X", "type": "string"}}, + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( MacAddress, { - 'properties': { - 'x': { - 'title': 'X', - 'type': 'string', + "properties": { + "x": { + "title": "X", + "type": "string", } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( Latitude, { - 'properties': { - 'x': { - 'maximum': 90.0, - 'minimum': -90.0, - 'title': 'X', - 'type': 'number', + "properties": { + "x": { + "maximum": 90.0, + "minimum": -90.0, + "title": "X", + "type": "number", } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( Longitude, { - 'properties': { - 'x': { - 'maximum': 180.0, - 'minimum': -180.0, - 'title': 'X', - 'type': 'number', + "properties": { + "x": { + "maximum": 180.0, + "minimum": -180.0, + "title": "X", + "type": "number", } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( Coordinate, { - '$defs': { - 'Coordinate': { - 'properties': { - 'latitude': {'maximum': 90.0, 'minimum': -90.0, 'title': 'Latitude', 'type': 'number'}, - 'longitude': {'maximum': 180.0, 'minimum': -180.0, 'title': 'Longitude', 'type': 'number'}, + "$defs": { + "Coordinate": { + "properties": { + "latitude": {"maximum": 90.0, "minimum": -90.0, "title": "Latitude", "type": "number"}, + "longitude": {"maximum": 180.0, "minimum": -180.0, "title": "Longitude", "type": "number"}, }, - 'required': ['latitude', 'longitude'], - 'title': 'Coordinate', - 'type': 'object', + "required": ["latitude", "longitude"], + "title": "Coordinate", + "type": "object", } }, - 'properties': { - 'x': { - 'anyOf': [ - {'$ref': '#/$defs/Coordinate'}, + "properties": { + "x": { + "anyOf": [ + {"$ref": "#/$defs/Coordinate"}, { - 'maxItems': 2, - 'minItems': 2, - 'prefixItems': [ - {'type': 'number'}, - {'type': 'number'}, + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + {"type": "number"}, + {"type": "number"}, ], - 'type': 'array', + "type": "array", }, - {'type': 'string'}, + {"type": "string"}, ], - 'title': 'X', + "title": "X", }, }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( ULID, { - 'properties': { - 'x': { - 'anyOf': [{'type': 'integer'}, {'format': 'binary', 'type': 'string'}, {'type': 'string'}], - 'title': 'X', + "properties": { + "x": { + "anyOf": [{"type": "integer"}, {"format": "binary", "type": "string"}, {"type": "string"}], + "title": "X", } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( ISBN, { - 'properties': { - 'x': { - 'title': 'X', - 'type': 'string', + "properties": { + "x": { + "title": "X", + "type": "string", } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( DateTime, { - 'properties': {'x': {'format': 'date-time', 'title': 'X', 'type': 'string'}}, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "properties": {"x": {"format": "date-time", "title": "X", "type": "string"}}, + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( ISO639_3, { - 'properties': { - 'x': { - 'title': 'X', - 'type': 'string', - 'enum': languages, - 'maxLength': 3, - 'minLength': 3, + "properties": { + "x": { + "title": "X", + "type": "string", + "enum": languages, + "maxLength": 3, + "minLength": 3, } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( ISO639_5, { - 'properties': { - 'x': { - 'title': 'X', - 'type': 'string', - 'enum': language_families, - 'maxLength': 3, - 'minLength': 3, + "properties": { + "x": { + "title": "X", + "type": "string", + "enum": language_families, + "maxLength": 3, + "minLength": 3, } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( ISO4217, { - 'properties': { - 'x': { - 'title': 'X', - 'type': 'string', - 'enum': currencies, - 'maxLength': 3, - 'minLength': 3, + "properties": { + "x": { + "title": "X", + "type": "string", + "enum": currencies, + "maxLength": 3, + "minLength": 3, } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ( Currency, { - 'properties': { - 'x': { - 'title': 'X', - 'type': 'string', - 'enum': everyday_currencies, - 'maxLength': 3, - 'minLength': 3, + "properties": { + "x": { + "title": "X", + "type": "string", + "enum": everyday_currencies, + "maxLength": 3, + "minLength": 3, } }, - 'required': ['x'], - 'title': 'Model', - 'type': 'object', + "required": ["x"], + "title": "Model", + "type": "object", }, ), ], diff --git a/tests/test_language_codes.py b/tests/test_language_codes.py index 27cc44ab..c6afce1d 100644 --- a/tests/test_language_codes.py +++ b/tests/test_language_codes.py @@ -3,7 +3,6 @@ import pycountry import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types import language_code @@ -15,39 +14,39 @@ class ISO5CheckingModel(BaseModel): lang: language_code.ISO639_5 -@pytest.mark.parametrize('lang', map(lambda lang: lang.alpha_3, pycountry.languages)) +@pytest.mark.parametrize("lang", map(lambda lang: lang.alpha_3, pycountry.languages)) def test_iso_ISO639_3_code_ok(lang: str): model = ISO3CheckingModel(lang=lang) assert model.lang == lang - assert model.model_dump() == {'lang': lang} # test serialization + assert model.model_dump() == {"lang": lang} # test serialization -@pytest.mark.parametrize('lang', map(lambda lang: lang.alpha_3, pycountry.language_families)) +@pytest.mark.parametrize("lang", map(lambda lang: lang.alpha_3, pycountry.language_families)) def test_iso_639_5_code_ok(lang: str): model = ISO5CheckingModel(lang=lang) assert model.lang == lang - assert model.model_dump() == {'lang': lang} # test serialization + assert model.model_dump() == {"lang": lang} # test serialization def test_iso3_language_fail(): with pytest.raises( ValidationError, match=re.escape( - '1 validation error for ISO3CheckingModel\nlang\n ' - 'Invalid ISO 639-3 language code. ' + "1 validation error for ISO3CheckingModel\nlang\n " + "Invalid ISO 639-3 language code. " "See https://en.wikipedia.org/wiki/ISO_639-3 [type=ISO649_3, input_value='LOL', input_type=str]" ), ): - ISO3CheckingModel(lang='LOL') + ISO3CheckingModel(lang="LOL") def test_iso5_language_fail(): with pytest.raises( ValidationError, match=re.escape( - '1 validation error for ISO5CheckingModel\nlang\n ' - 'Invalid ISO 639-5 language code. ' + "1 validation error for ISO5CheckingModel\nlang\n " + "Invalid ISO 639-5 language code. " "See https://en.wikipedia.org/wiki/ISO_639-5 [type=ISO649_5, input_value='LOL', input_type=str]" ), ): - ISO5CheckingModel(lang='LOL') + ISO5CheckingModel(lang="LOL") diff --git a/tests/test_mac_address.py b/tests/test_mac_address.py index 8d7455cf..b74ca6de 100644 --- a/tests/test_mac_address.py +++ b/tests/test_mac_address.py @@ -2,7 +2,6 @@ import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.mac_address import MacAddress @@ -11,50 +10,50 @@ class Network(BaseModel): @pytest.mark.parametrize( - 'mac_address, result, valid', + "mac_address, result, valid", [ # Valid MAC addresses - ('00:00:5e:00:53:01', '00:00:5e:00:53:01', True), - ('02:00:5e:10:00:00:00:01', '02:00:5e:10:00:00:00:01', True), + ("00:00:5e:00:53:01", "00:00:5e:00:53:01", True), + ("02:00:5e:10:00:00:00:01", "02:00:5e:10:00:00:00:01", True), ( - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", True, ), - ('00-00-5e-00-53-01', '00:00:5e:00:53:01', True), - ('02-00-5e-10-00-00-00-01', '02:00:5e:10:00:00:00:01', True), + ("00-00-5e-00-53-01", "00:00:5e:00:53:01", True), + ("02-00-5e-10-00-00-00-01", "02:00:5e:10:00:00:00:01", True), ( - '00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01', - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01", + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", True, ), - ('0000.5e00.5301', '00:00:5e:00:53:01', True), - ('0200.5e10.0000.0001', '02:00:5e:10:00:00:00:01', True), + ("0000.5e00.5301", "00:00:5e:00:53:01", True), + ("0200.5e10.0000.0001", "02:00:5e:10:00:00:00:01", True), ( - '0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001', - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001", + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", True, ), # Invalid MAC addresses - ('0200.5e10.0000.001', None, False), - ('00-00-5e-00-53-0', None, False), - ('00:00:5e:00:53:1', None, False), - ('02:00:5e:10:00:00:00:1', None, False), - ('00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:1', None, False), - ('0200.5e10.0000.001', None, False), # Invalid length - ('00-00-5e-00-53-0', None, False), # Missing character - ('00:00:5e:00:53:1', None, False), # Missing leading zero - ('00:00:5g:00:53:01', None, False), # Invalid hex digit 'g' - ('00.00.5e.0.3.01.0.0.5e.0.53.01', None, False), - ('00-00-5e-00-53-01:', None, False), # Extra separator at the end - ('00000.5e000.5301', None, False), - ('000.5e0.530001', None, False), - ('0000.5e#0./301', None, False), - (b'12.!4.5!.7/.#G.AB......', None, False), - ('12.!4.5!.7/.#G.AB', None, False), - ('00-00-5e-00-53-01-', None, False), # Extra separator at the end - ('00.00.5e.00.53.01.', None, False), # Extra separator at the end - ('00:00:5e:00:53:', None, False), # Incomplete MAC address + ("0200.5e10.0000.001", None, False), + ("00-00-5e-00-53-0", None, False), + ("00:00:5e:00:53:1", None, False), + ("02:00:5e:10:00:00:00:1", None, False), + ("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:1", None, False), + ("0200.5e10.0000.001", None, False), # Invalid length + ("00-00-5e-00-53-0", None, False), # Missing character + ("00:00:5e:00:53:1", None, False), # Missing leading zero + ("00:00:5g:00:53:01", None, False), # Invalid hex digit 'g' + ("00.00.5e.0.3.01.0.0.5e.0.53.01", None, False), + ("00-00-5e-00-53-01:", None, False), # Extra separator at the end + ("00000.5e000.5301", None, False), + ("000.5e0.530001", None, False), + ("0000.5e#0./301", None, False), + (b"12.!4.5!.7/.#G.AB......", None, False), + ("12.!4.5!.7/.#G.AB", None, False), + ("00-00-5e-00-53-01-", None, False), # Extra separator at the end + ("00.00.5e.00.53.01.", None, False), # Extra separator at the end + ("00:00:5e:00:53:", None, False), # Incomplete MAC address (float(12345678910111213), None, False), ], ) @@ -62,66 +61,66 @@ def test_format_for_mac_address(mac_address: Any, result: str, valid: bool): if valid: assert Network(mac_address=MacAddress(mac_address)).mac_address == result else: - with pytest.raises(ValidationError, match='format'): + with pytest.raises(ValidationError, match="format"): Network(mac_address=MacAddress(mac_address)) @pytest.mark.parametrize( - 'mac_address, result, valid', + "mac_address, result, valid", [ # Valid MAC addresses - ('00:00:5e:00:53:01', '00:00:5e:00:53:01', True), - ('02:00:5e:10:00:00:00:01', '02:00:5e:10:00:00:00:01', True), + ("00:00:5e:00:53:01", "00:00:5e:00:53:01", True), + ("02:00:5e:10:00:00:00:01", "02:00:5e:10:00:00:00:01", True), ( - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", True, ), - ('00-00-5e-00-53-01', '00:00:5e:00:53:01', True), - ('02-00-5e-10-00-00-00-01', '02:00:5e:10:00:00:00:01', True), + ("00-00-5e-00-53-01", "00:00:5e:00:53:01", True), + ("02-00-5e-10-00-00-00-01", "02:00:5e:10:00:00:00:01", True), ( - '00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01', - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01", + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", True, ), - ('0000.5e00.5301', '00:00:5e:00:53:01', True), - ('0200.5e10.0000.0001', '02:00:5e:10:00:00:00:01', True), + ("0000.5e00.5301", "00:00:5e:00:53:01", True), + ("0200.5e10.0000.0001", "02:00:5e:10:00:00:00:01", True), ( - '0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001', - '00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01', + "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001", + "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", True, ), # Invalid MAC addresses - ('0', None, False), - ('00:00:00', None, False), - ('00-00-5e-00-53-01-01', None, False), - ('0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001.0000.0001', None, False), + ("0", None, False), + ("00:00:00", None, False), + ("00-00-5e-00-53-01-01", None, False), + ("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001.0000.0001", None, False), ], ) def test_length_for_mac_address(mac_address: str, result: str, valid: bool): if valid: assert Network(mac_address=MacAddress(mac_address)).mac_address == result else: - with pytest.raises(ValueError, match='Length'): + with pytest.raises(ValueError, match="Length"): Network(mac_address=MacAddress(mac_address)) @pytest.mark.parametrize( - 'mac_address, valid', + "mac_address, valid", [ # Valid MAC addresses - ('00:00:5e:00:53:01', True), - (MacAddress('00:00:5e:00:53:01'), True), + ("00:00:5e:00:53:01", True), + (MacAddress("00:00:5e:00:53:01"), True), # Invalid MAC addresses (0, False), - (['00:00:00'], False), + (["00:00:00"], False), ], ) def test_type_for_mac_address(mac_address: Any, valid: bool): if valid: Network(mac_address=MacAddress(mac_address)) else: - with pytest.raises(ValidationError, match='MAC address must be 14'): + with pytest.raises(ValidationError, match="MAC address must be 14"): Network(mac_address=MacAddress(mac_address)) @@ -129,16 +128,16 @@ def test_model_validation(): class Model(BaseModel): mac_address: MacAddress - assert Model(mac_address='00:00:5e:00:53:01').mac_address == '00:00:5e:00:53:01' + assert Model(mac_address="00:00:5e:00:53:01").mac_address == "00:00:5e:00:53:01" with pytest.raises(ValidationError) as exc_info: - Model(mac_address='1234') + Model(mac_address="1234") assert exc_info.value.errors() == [ { - 'ctx': {'mac_address': '1234', 'required_length': 14}, - 'input': '1234', - 'loc': ('mac_address',), - 'msg': 'Length for a 1234 MAC address must be 14', - 'type': 'mac_address_len', + "ctx": {"mac_address": "1234", "required_length": 14}, + "input": "1234", + "loc": ("mac_address",), + "msg": "Length for a 1234 MAC address must be 14", + "type": "mac_address_len", } ] diff --git a/tests/test_pendulum_dt.py b/tests/test_pendulum_dt.py index 31306d77..6801fa78 100644 --- a/tests/test_pendulum_dt.py +++ b/tests/test_pendulum_dt.py @@ -1,7 +1,6 @@ import pendulum import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.pendulum_dt import DateTime @@ -19,7 +18,7 @@ def test_pendulum_dt_existing_instance(): @pytest.mark.parametrize( - 'dt', [pendulum.now().to_iso8601_string(), pendulum.now().to_w3c_string(), pendulum.now().to_iso8601_string()] + "dt", [pendulum.now().to_iso8601_string(), pendulum.now().to_w3c_string(), pendulum.now().to_iso8601_string()] ) def test_pendulum_dt_from_serialized(dt): """ @@ -30,7 +29,7 @@ def test_pendulum_dt_from_serialized(dt): assert model.dt == dt_actual -@pytest.mark.parametrize('dt', [None, 'malformed', pendulum.now().to_iso8601_string()[:5], 42]) +@pytest.mark.parametrize("dt", [None, "malformed", pendulum.now().to_iso8601_string()[:5], 42]) def test_pendulum_dt_malformed(dt): """ Verifies that the instance fails to validate if malformed dt are passed. diff --git a/tests/test_phone_numbers.py b/tests/test_phone_numbers.py index 1d3a1051..4e34aadc 100644 --- a/tests/test_phone_numbers.py +++ b/tests/test_phone_numbers.py @@ -2,7 +2,6 @@ import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.phone_numbers import PhoneNumber @@ -12,58 +11,58 @@ class Something(BaseModel): # Note: the 555 area code will result in an invalid phone number def test_valid_phone_number() -> None: - Something(phone_number='+1 901 555 1212') + Something(phone_number="+1 901 555 1212") def test_when_extension_provided() -> None: - Something(phone_number='+1 901 555 1212 ext 12533') + Something(phone_number="+1 901 555 1212 ext 12533") -@pytest.mark.parametrize('invalid_number', ['', '123', 12, None, object(), '55 121']) +@pytest.mark.parametrize("invalid_number", ["", "123", 12, None, object(), "55 121"]) def test_invalid_phone_number(invalid_number: Any) -> None: with pytest.raises(ValidationError): Something(phone_number=invalid_number) def test_formats_phone_number() -> None: - result = Something(phone_number='+1 901 555 1212 ext 12533') - assert result.phone_number == 'tel:+1-901-555-1212;ext=12533' + result = Something(phone_number="+1 901 555 1212 ext 12533") + assert result.phone_number == "tel:+1-901-555-1212;ext=12533" def test_supported_regions() -> None: - assert 'US' in PhoneNumber.supported_regions - assert 'GB' in PhoneNumber.supported_regions + assert "US" in PhoneNumber.supported_regions + assert "GB" in PhoneNumber.supported_regions def test_supported_formats() -> None: - assert 'E164' in PhoneNumber.supported_formats - assert 'RFC3966' in PhoneNumber.supported_formats - assert '__dict__' not in PhoneNumber.supported_formats - assert 'to_string' not in PhoneNumber.supported_formats + assert "E164" in PhoneNumber.supported_formats + assert "RFC3966" in PhoneNumber.supported_formats + assert "__dict__" not in PhoneNumber.supported_formats + assert "to_string" not in PhoneNumber.supported_formats def test_parse_error() -> None: - with pytest.raises(ValidationError, match='value is not a valid phone number'): - Something(phone_number='555 1212') + with pytest.raises(ValidationError, match="value is not a valid phone number"): + Something(phone_number="555 1212") def test_parsed_but_not_a_valid_number() -> None: - with pytest.raises(ValidationError, match='value is not a valid phone number'): - Something(phone_number='+1 555-1212') + with pytest.raises(ValidationError, match="value is not a valid phone number"): + Something(phone_number="+1 555-1212") def test_json_schema() -> None: assert Something.model_json_schema() == { - 'title': 'Something', - 'type': 'object', - 'properties': { - 'phone_number': { - 'title': 'Phone Number', - 'type': 'string', - 'format': 'phone', - 'minLength': 7, - 'maxLength': 64, + "title": "Something", + "type": "object", + "properties": { + "phone_number": { + "title": "Phone Number", + "type": "string", + "format": "phone", + "minLength": 7, + "maxLength": 64, } }, - 'required': ['phone_number'], + "required": ["phone_number"], } diff --git a/tests/test_routing_number.py b/tests/test_routing_number.py index d73eeca7..20cf7883 100644 --- a/tests/test_routing_number.py +++ b/tests/test_routing_number.py @@ -2,7 +2,6 @@ import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.routing_number import ABARoutingNumber @@ -10,32 +9,32 @@ class Model(BaseModel): routing_number: ABARoutingNumber -@pytest.mark.parametrize('routing_number', [12, None, object(), 123456789]) +@pytest.mark.parametrize("routing_number", [12, None, object(), 123456789]) def test_invalid_routing_number_string(routing_number: Any) -> None: with pytest.raises(ValidationError) as validation_error: Model(routing_number=routing_number) - assert validation_error.match('Input should be a valid string') + assert validation_error.match("Input should be a valid string") -@pytest.mark.parametrize('routing_number', ['', '123', '1234567890']) +@pytest.mark.parametrize("routing_number", ["", "123", "1234567890"]) def test_invalid_routing_number_length(routing_number: Any) -> None: with pytest.raises(ValidationError) as validation_error: Model(routing_number=routing_number) - assert validation_error.match(r'String should have at (most|least) 9 characters') + assert validation_error.match(r"String should have at (most|least) 9 characters") -@pytest.mark.parametrize('routing_number', ['122105154', '122235822', '123103723', '074900781']) +@pytest.mark.parametrize("routing_number", ["122105154", "122235822", "123103723", "074900781"]) def test_invalid_routing_number(routing_number: Any) -> None: with pytest.raises(ValidationError) as validation_error: Model(routing_number=routing_number) - assert validation_error.match('Incorrect ABA routing transit number') + assert validation_error.match("Incorrect ABA routing transit number") -@pytest.mark.parametrize('routing_number', ['122105155', '122235821', '123103729', '074900783']) +@pytest.mark.parametrize("routing_number", ["122105155", "122235821", "123103729", "074900783"]) def test_valid_routing_number(routing_number: str) -> None: Model(routing_number=routing_number) def test_raises_error_when_not_a_string() -> None: - with pytest.raises(ValidationError, match='routing number is not all digits'): - Model(routing_number='A12210515') + with pytest.raises(ValidationError, match="routing number is not all digits"): + Model(routing_number="A12210515") diff --git a/tests/test_types_color.py b/tests/test_types_color.py index defec19d..43c08333 100644 --- a/tests/test_types_color.py +++ b/tests/test_types_color.py @@ -3,66 +3,65 @@ import pytest from pydantic import BaseModel, ValidationError from pydantic_core import PydanticCustomError - from pydantic_extra_types.color import Color @pytest.mark.parametrize( - 'raw_color, as_tuple', + "raw_color, as_tuple", [ # named colors - ('aliceblue', (240, 248, 255)), - ('Antiquewhite', (250, 235, 215)), - ('transparent', (0, 0, 0, 0)), - ('#000000', (0, 0, 0)), - ('#DAB', (221, 170, 187)), - ('#dab', (221, 170, 187)), - ('#000', (0, 0, 0)), - ('0x797979', (121, 121, 121)), - ('0x777', (119, 119, 119)), - ('0x777777', (119, 119, 119)), - ('0x777777cc', (119, 119, 119, 0.8)), - ('777', (119, 119, 119)), - ('777c', (119, 119, 119, 0.8)), - (' 777', (119, 119, 119)), - ('777 ', (119, 119, 119)), - (' 777 ', (119, 119, 119)), + ("aliceblue", (240, 248, 255)), + ("Antiquewhite", (250, 235, 215)), + ("transparent", (0, 0, 0, 0)), + ("#000000", (0, 0, 0)), + ("#DAB", (221, 170, 187)), + ("#dab", (221, 170, 187)), + ("#000", (0, 0, 0)), + ("0x797979", (121, 121, 121)), + ("0x777", (119, 119, 119)), + ("0x777777", (119, 119, 119)), + ("0x777777cc", (119, 119, 119, 0.8)), + ("777", (119, 119, 119)), + ("777c", (119, 119, 119, 0.8)), + (" 777", (119, 119, 119)), + ("777 ", (119, 119, 119)), + (" 777 ", (119, 119, 119)), ((0, 0, 128), (0, 0, 128)), ([0, 0, 128], (0, 0, 128)), ((0, 0, 205, 1.0), (0, 0, 205)), ((0, 0, 205, 0.5), (0, 0, 205, 0.5)), - ('rgb(0, 0, 205)', (0, 0, 205)), - ('rgb(0, 0, 205.2)', (0, 0, 205)), - ('rgb(0, 0.2, 205)', (0, 0, 205)), - ('rgba(0, 0, 128, 0.6)', (0, 0, 128, 0.6)), - ('rgba(0, 0, 128, .6)', (0, 0, 128, 0.6)), - ('rgba(0, 0, 128, 60%)', (0, 0, 128, 0.6)), - (' rgba(0, 0, 128,0.6) ', (0, 0, 128, 0.6)), - ('rgba(00,0,128,0.6 )', (0, 0, 128, 0.6)), - ('rgba(0, 0, 128, 0)', (0, 0, 128, 0)), - ('rgba(0, 0, 128, 1)', (0, 0, 128)), - ('rgb(0 0.2 205)', (0, 0, 205)), - ('rgb(0 0.2 205 / 0.6)', (0, 0, 205, 0.6)), - ('rgb(0 0.2 205 / 60%)', (0, 0, 205, 0.6)), - ('rgba(0 0 128)', (0, 0, 128)), - ('rgba(0 0 128 / 0.6)', (0, 0, 128, 0.6)), - ('rgba(0 0 128 / 60%)', (0, 0, 128, 0.6)), - ('hsl(270, 60%, 70%)', (178, 133, 224)), - ('hsl(180, 100%, 50%)', (0, 255, 255)), - ('hsl(630, 60%, 70%)', (178, 133, 224)), - ('hsl(270deg, 60%, 70%)', (178, 133, 224)), - ('hsl(.75turn, 60%, 70%)', (178, 133, 224)), - ('hsl(-.25turn, 60%, 70%)', (178, 133, 224)), - ('hsl(-0.25turn, 60%, 70%)', (178, 133, 224)), - ('hsl(4.71238rad, 60%, 70%)', (178, 133, 224)), - ('hsl(10.9955rad, 60%, 70%)', (178, 133, 224)), - ('hsl(270, 60%, 50%, .15)', (127, 51, 204, 0.15)), - ('hsl(270.00deg, 60%, 50%, 15%)', (127, 51, 204, 0.15)), - ('hsl(630 60% 70%)', (178, 133, 224)), - ('hsl(270 60% 50% / .15)', (127, 51, 204, 0.15)), - ('hsla(630, 60%, 70%)', (178, 133, 224)), - ('hsla(630 60% 70%)', (178, 133, 224)), - ('hsla(270 60% 50% / .15)', (127, 51, 204, 0.15)), + ("rgb(0, 0, 205)", (0, 0, 205)), + ("rgb(0, 0, 205.2)", (0, 0, 205)), + ("rgb(0, 0.2, 205)", (0, 0, 205)), + ("rgba(0, 0, 128, 0.6)", (0, 0, 128, 0.6)), + ("rgba(0, 0, 128, .6)", (0, 0, 128, 0.6)), + ("rgba(0, 0, 128, 60%)", (0, 0, 128, 0.6)), + (" rgba(0, 0, 128,0.6) ", (0, 0, 128, 0.6)), + ("rgba(00,0,128,0.6 )", (0, 0, 128, 0.6)), + ("rgba(0, 0, 128, 0)", (0, 0, 128, 0)), + ("rgba(0, 0, 128, 1)", (0, 0, 128)), + ("rgb(0 0.2 205)", (0, 0, 205)), + ("rgb(0 0.2 205 / 0.6)", (0, 0, 205, 0.6)), + ("rgb(0 0.2 205 / 60%)", (0, 0, 205, 0.6)), + ("rgba(0 0 128)", (0, 0, 128)), + ("rgba(0 0 128 / 0.6)", (0, 0, 128, 0.6)), + ("rgba(0 0 128 / 60%)", (0, 0, 128, 0.6)), + ("hsl(270, 60%, 70%)", (178, 133, 224)), + ("hsl(180, 100%, 50%)", (0, 255, 255)), + ("hsl(630, 60%, 70%)", (178, 133, 224)), + ("hsl(270deg, 60%, 70%)", (178, 133, 224)), + ("hsl(.75turn, 60%, 70%)", (178, 133, 224)), + ("hsl(-.25turn, 60%, 70%)", (178, 133, 224)), + ("hsl(-0.25turn, 60%, 70%)", (178, 133, 224)), + ("hsl(4.71238rad, 60%, 70%)", (178, 133, 224)), + ("hsl(10.9955rad, 60%, 70%)", (178, 133, 224)), + ("hsl(270, 60%, 50%, .15)", (127, 51, 204, 0.15)), + ("hsl(270.00deg, 60%, 50%, 15%)", (127, 51, 204, 0.15)), + ("hsl(630 60% 70%)", (178, 133, 224)), + ("hsl(270 60% 50% / .15)", (127, 51, 204, 0.15)), + ("hsla(630, 60%, 70%)", (178, 133, 224)), + ("hsla(630 60% 70%)", (178, 133, 224)), + ("hsla(270 60% 50% / .15)", (127, 51, 204, 0.15)), ], ) def test_color_success(raw_color, as_tuple): @@ -72,39 +71,39 @@ def test_color_success(raw_color, as_tuple): @pytest.mark.parametrize( - 'color', + "color", [ # named colors - 'nosuchname', - 'chucknorris', + "nosuchname", + "chucknorris", # hex - '#0000000', - 'x000', + "#0000000", + "x000", # rgb/rgba tuples (256, 256, 256), (128, 128, 128, 0.5, 128), - (0, 0, 'x'), + (0, 0, "x"), (0, 0, 0, 1.5), - (0, 0, 0, 'x'), + (0, 0, 0, "x"), (0, 0, 1280), (0, 0, 1205, 0.1), (0, 0, 1128, 0.5), (0, 0, 1128, -0.5), (0, 0, 1128, 1.5), # rgb/rgba strings - 'rgb(0, 0, 1205)', - 'rgb(0, 0, 1128)', - 'rgb(0, 0, 200 / 0.2)', - 'rgb(72 122 18, 0.3)', - 'rgba(0, 0, 11205, 0.1)', - 'rgba(0, 0, 128, 11.5)', - 'rgba(0, 0, 128 / 11.5)', - 'rgba(72 122 18 0.3)', + "rgb(0, 0, 1205)", + "rgb(0, 0, 1128)", + "rgb(0, 0, 200 / 0.2)", + "rgb(72 122 18, 0.3)", + "rgba(0, 0, 11205, 0.1)", + "rgba(0, 0, 128, 11.5)", + "rgba(0, 0, 128 / 11.5)", + "rgba(72 122 18 0.3)", # hsl/hsla strings - 'hsl(180, 101%, 50%)', - 'hsl(72 122 18 / 0.3)', - 'hsl(630 60% 70%, 0.3)', - 'hsla(72 122 18 / 0.3)', + "hsl(180, 101%, 50%)", + "hsl(72 122 18 / 0.3)", + "hsl(630 60% 70%, 0.3)", + "hsla(72 122 18 / 0.3)", # neither a tuple, not a string datetime(2017, 10, 5, 19, 47, 7), object, @@ -114,32 +113,32 @@ def test_color_success(raw_color, as_tuple): def test_color_fail(color): with pytest.raises(PydanticCustomError) as exc_info: Color(color) - assert exc_info.value.type == 'color_error' + assert exc_info.value.type == "color_error" def test_model_validation(): class Model(BaseModel): color: Color - assert Model(color='red').color.as_hex() == '#f00' - assert Model(color=Color('red')).color.as_hex() == '#f00' + assert Model(color="red").color.as_hex() == "#f00" + assert Model(color=Color("red")).color.as_hex() == "#f00" with pytest.raises(ValidationError) as exc_info: - Model(color='snot') + Model(color="snot") # insert_assert(exc_info.value.errors()) assert exc_info.value.errors() == [ { - 'type': 'color_error', - 'loc': ('color',), - 'msg': 'value is not a valid color: string not recognised as a valid color', - 'input': 'snot', + "type": "color_error", + "loc": ("color",), + "msg": "value is not a valid color: string not recognised as a valid color", + "input": "snot", } ] def test_as_rgb(): - assert Color('bad').as_rgb() == 'rgb(187, 170, 221)' - assert Color((1, 2, 3, 0.123456)).as_rgb() == 'rgba(1, 2, 3, 0.12)' - assert Color((1, 2, 3, 0.1)).as_rgb() == 'rgba(1, 2, 3, 0.1)' + assert Color("bad").as_rgb() == "rgb(187, 170, 221)" + assert Color((1, 2, 3, 0.123456)).as_rgb() == "rgba(1, 2, 3, 0.12)" + assert Color((1, 2, 3, 0.1)).as_rgb() == "rgba(1, 2, 3, 0.1)" def test_as_rgb_tuple(): @@ -156,13 +155,13 @@ def test_as_rgb_tuple(): def test_as_hsl(): - assert Color('bad').as_hsl() == 'hsl(260, 43%, 77%)' - assert Color((1, 2, 3, 0.123456)).as_hsl() == 'hsl(210, 50%, 1%, 0.12)' - assert Color('hsl(260, 43%, 77%)').as_hsl() == 'hsl(260, 43%, 77%)' + assert Color("bad").as_hsl() == "hsl(260, 43%, 77%)" + assert Color((1, 2, 3, 0.123456)).as_hsl() == "hsl(210, 50%, 1%, 0.12)" + assert Color("hsl(260, 43%, 77%)").as_hsl() == "hsl(260, 43%, 77%)" def test_as_hsl_tuple(): - c = Color('016997') + c = Color("016997") h, s, l_, a = c.as_hsl_tuple(alpha=True) assert h == pytest.approx(0.551, rel=0.01) assert s == pytest.approx(0.986, rel=0.01) @@ -178,53 +177,53 @@ def test_as_hsl_tuple(): def test_as_hex(): - assert Color((1, 2, 3)).as_hex() == '#010203' - assert Color((119, 119, 119)).as_hex() == '#777' - assert Color((119, 0, 238)).as_hex() == '#70e' - assert Color('B0B').as_hex() == '#b0b' - assert Color((1, 2, 3, 0.123456)).as_hex() == '#0102031f' - assert Color((1, 2, 3, 0.1)).as_hex() == '#0102031a' + assert Color((1, 2, 3)).as_hex() == "#010203" + assert Color((119, 119, 119)).as_hex() == "#777" + assert Color((119, 0, 238)).as_hex() == "#70e" + assert Color("B0B").as_hex() == "#b0b" + assert Color((1, 2, 3, 0.123456)).as_hex() == "#0102031f" + assert Color((1, 2, 3, 0.1)).as_hex() == "#0102031a" def test_as_hex_long(): - assert Color((1, 2, 3)).as_hex(format='long') == '#010203' - assert Color((119, 119, 119)).as_hex(format='long') == '#777777' - assert Color((119, 0, 238)).as_hex(format='long') == '#7700ee' - assert Color('B0B').as_hex(format='long') == '#bb00bb' - assert Color('#0102031a').as_hex(format='long') == '#0102031a' + assert Color((1, 2, 3)).as_hex(format="long") == "#010203" + assert Color((119, 119, 119)).as_hex(format="long") == "#777777" + assert Color((119, 0, 238)).as_hex(format="long") == "#7700ee" + assert Color("B0B").as_hex(format="long") == "#bb00bb" + assert Color("#0102031a").as_hex(format="long") == "#0102031a" def test_as_named(): - assert Color((0, 255, 255)).as_named() == 'cyan' - assert Color('#808000').as_named() == 'olive' - assert Color('hsl(180, 100%, 50%)').as_named() == 'cyan' + assert Color((0, 255, 255)).as_named() == "cyan" + assert Color("#808000").as_named() == "olive" + assert Color("hsl(180, 100%, 50%)").as_named() == "cyan" - assert Color((240, 248, 255)).as_named() == 'aliceblue' + assert Color((240, 248, 255)).as_named() == "aliceblue" with pytest.raises(ValueError) as exc_info: Color((1, 2, 3)).as_named() - assert exc_info.value.args[0] == 'no named color found, use fallback=True, as_hex() or as_rgb()' + assert exc_info.value.args[0] == "no named color found, use fallback=True, as_hex() or as_rgb()" - assert Color((1, 2, 3)).as_named(fallback=True) == '#010203' - assert Color((1, 2, 3, 0.1)).as_named(fallback=True) == '#0102031a' + assert Color((1, 2, 3)).as_named(fallback=True) == "#010203" + assert Color((1, 2, 3, 0.1)).as_named(fallback=True) == "#0102031a" def test_str_repr(): - assert str(Color('red')) == 'red' - assert repr(Color('red')) == "Color('red', rgb=(255, 0, 0))" - assert str(Color((1, 2, 3))) == '#010203' + assert str(Color("red")) == "red" + assert repr(Color("red")) == "Color('red', rgb=(255, 0, 0))" + assert str(Color((1, 2, 3))) == "#010203" assert repr(Color((1, 2, 3))) == "Color('#010203', rgb=(1, 2, 3))" def test_eq(): - assert Color('red') == Color('red') - assert Color('red') != Color('blue') - assert Color('red') != 'red' + assert Color("red") == Color("red") + assert Color("red") != Color("blue") + assert Color("red") != "red" - assert Color('red') == Color((255, 0, 0)) - assert Color('red') != Color((0, 0, 255)) + assert Color("red") == Color((255, 0, 0)) + assert Color("red") != Color((0, 0, 255)) def test_color_hashable(): - assert hash(Color('red')) != hash(Color('blue')) - assert hash(Color('red')) == hash(Color((255, 0, 0))) - assert hash(Color('red')) != hash(Color((255, 0, 0, 0.5))) + assert hash(Color("red")) != hash(Color("blue")) + assert hash(Color("red")) == hash(Color((255, 0, 0))) + assert hash(Color("red")) != hash(Color((255, 0, 0, 0.5))) diff --git a/tests/test_types_payment.py b/tests/test_types_payment.py index bd8fd905..c1e16d96 100644 --- a/tests/test_types_payment.py +++ b/tests/test_types_payment.py @@ -4,40 +4,39 @@ import pytest from pydantic import BaseModel, ValidationError from pydantic_core._pydantic_core import PydanticCustomError - from pydantic_extra_types.payment import PaymentCardBrand, PaymentCardNumber -VALID_AMEX = '370000000000002' -VALID_MC = '5100000000000003' -VALID_VISA_13 = '4050000000001' -VALID_VISA_16 = '4050000000000001' -VALID_VISA_19 = '4050000000000000001' -VALID_MIR_16 = '2200000000000004' -VALID_MIR_17 = '22000000000000004' -VALID_MIR_18 = '220000000000000004' -VALID_MIR_19 = '2200000000000000004' -VALID_DISCOVER = '6011000000000004' -VALID_VERVE_16 = '5061000000000001' -VALID_VERVE_18 = '506100000000000001' -VALID_VERVE_19 = '5061000000000000001' -VALID_DANKORT = '5019000000000000' -VALID_UNIONPAY_16 = '6200000000000001' -VALID_UNIONPAY_19 = '8100000000000000001' -VALID_JCB_16 = '3528000000000001' -VALID_JCB_19 = '3528000000000000001' -VALID_MAESTRO = '6759649826438453' -VALID_TROY = '9792000000000001' -VALID_OTHER = '2000000000000000008' -LUHN_INVALID = '4000000000000000' -LEN_INVALID = '40000000000000006' +VALID_AMEX = "370000000000002" +VALID_MC = "5100000000000003" +VALID_VISA_13 = "4050000000001" +VALID_VISA_16 = "4050000000000001" +VALID_VISA_19 = "4050000000000000001" +VALID_MIR_16 = "2200000000000004" +VALID_MIR_17 = "22000000000000004" +VALID_MIR_18 = "220000000000000004" +VALID_MIR_19 = "2200000000000000004" +VALID_DISCOVER = "6011000000000004" +VALID_VERVE_16 = "5061000000000001" +VALID_VERVE_18 = "506100000000000001" +VALID_VERVE_19 = "5061000000000000001" +VALID_DANKORT = "5019000000000000" +VALID_UNIONPAY_16 = "6200000000000001" +VALID_UNIONPAY_19 = "8100000000000000001" +VALID_JCB_16 = "3528000000000001" +VALID_JCB_19 = "3528000000000000001" +VALID_MAESTRO = "6759649826438453" +VALID_TROY = "9792000000000001" +VALID_OTHER = "2000000000000000008" +LUHN_INVALID = "4000000000000000" +LEN_INVALID = "40000000000000006" # Mock PaymentCardNumber -PCN = namedtuple('PaymentCardNumber', ['card_number', 'brand']) +PCN = namedtuple("PaymentCardNumber", ["card_number", "brand"]) PCN.__len__ = lambda v: len(v.card_number) -@pytest.fixture(scope='session', name='PaymentCard') +@pytest.fixture(scope="session", name="PaymentCard") def payment_card_model_fixture(): class PaymentCard(BaseModel): card_number: PaymentCardNumber @@ -46,55 +45,55 @@ class PaymentCard(BaseModel): def test_validate_digits(): - digits = '12345' + digits = "12345" assert PaymentCardNumber.validate_digits(digits) is None - with pytest.raises(PydanticCustomError, match='Card number is not all digits'): - PaymentCardNumber.validate_digits('hello') - with pytest.raises(PydanticCustomError, match='Card number is not all digits'): - PaymentCardNumber.validate_digits('²') + with pytest.raises(PydanticCustomError, match="Card number is not all digits"): + PaymentCardNumber.validate_digits("hello") + with pytest.raises(PydanticCustomError, match="Card number is not all digits"): + PaymentCardNumber.validate_digits("²") @pytest.mark.parametrize( - 'card_number, valid', + "card_number, valid", [ - ('0', True), - ('00', True), - ('18', True), - ('0000000000000000', True), - ('4242424242424240', False), - ('4242424242424241', False), - ('4242424242424242', True), - ('4242424242424243', False), - ('4242424242424244', False), - ('4242424242424245', False), - ('4242424242424246', False), - ('4242424242424247', False), - ('4242424242424248', False), - ('4242424242424249', False), - ('42424242424242426', True), - ('424242424242424267', True), - ('4242424242424242675', True), - ('5164581347216566', True), - ('4345351087414150', True), - ('343728738009846', True), - ('5164581347216567', False), - ('4345351087414151', False), - ('343728738009847', False), - ('000000018', True), - ('99999999999999999999', True), - ('99999999999999999999999999999999999999999999999999999999999999999997', True), + ("0", True), + ("00", True), + ("18", True), + ("0000000000000000", True), + ("4242424242424240", False), + ("4242424242424241", False), + ("4242424242424242", True), + ("4242424242424243", False), + ("4242424242424244", False), + ("4242424242424245", False), + ("4242424242424246", False), + ("4242424242424247", False), + ("4242424242424248", False), + ("4242424242424249", False), + ("42424242424242426", True), + ("424242424242424267", True), + ("4242424242424242675", True), + ("5164581347216566", True), + ("4345351087414150", True), + ("343728738009846", True), + ("5164581347216567", False), + ("4345351087414151", False), + ("343728738009847", False), + ("000000018", True), + ("99999999999999999999", True), + ("99999999999999999999999999999999999999999999999999999999999999999997", True), ], ) def test_validate_luhn_check_digit(card_number: str, valid: bool): if valid: assert PaymentCardNumber.validate_luhn_check_digit(card_number) == card_number else: - with pytest.raises(PydanticCustomError, match='Card number is not luhn valid'): + with pytest.raises(PydanticCustomError, match="Card number is not luhn valid"): PaymentCardNumber.validate_luhn_check_digit(card_number) @pytest.mark.parametrize( - 'card_number, brand, valid', + "card_number, brand, valid", [ (VALID_VISA_13, PaymentCardBrand.visa, True), (VALID_VISA_16, PaymentCardBrand.visa, True), @@ -127,11 +126,11 @@ def test_length_for_brand(card_number: str, brand: PaymentCardBrand, valid: bool else: with pytest.raises(PydanticCustomError) as exc_info: PaymentCardNumber.validate_brand(card_number) - assert exc_info.value.type == 'payment_card_number_brand' + assert exc_info.value.type == "payment_card_number_brand" @pytest.mark.parametrize( - 'card_number, brand', + "card_number, brand", [ (VALID_AMEX, PaymentCardBrand.amex), (VALID_MC, PaymentCardBrand.mastercard), @@ -154,18 +153,18 @@ def test_get_brand(card_number: str, brand: PaymentCardBrand): def test_valid(PaymentCard): card = PaymentCard(card_number=VALID_VISA_16) assert str(card.card_number) == VALID_VISA_16 - assert card.card_number.masked == '405000******0001' + assert card.card_number.masked == "405000******0001" @pytest.mark.parametrize( - 'card_number, error_message', + "card_number, error_message", [ - (None, 'type=string_type'), - ('1' * 11, 'type=string_too_short,'), - ('1' * 20, 'type=string_too_long,'), - ('h' * 16, 'type=payment_card_number_digits'), - (LUHN_INVALID, 'type=payment_card_number_luhn,'), - (LEN_INVALID, 'type=payment_card_number_brand,'), + (None, "type=string_type"), + ("1" * 11, "type=string_too_short,"), + ("1" * 20, "type=string_too_long,"), + ("h" * 16, "type=payment_card_number_digits"), + (LUHN_INVALID, "type=payment_card_number_luhn,"), + (LEN_INVALID, "type=payment_card_number_brand,"), ], ) def test_error_types(card_number: Any, error_message: str, PaymentCard): @@ -175,12 +174,12 @@ def test_error_types(card_number: Any, error_message: str, PaymentCard): def test_payment_card_brand(): b = PaymentCardBrand.visa - assert str(b) == 'Visa' + assert str(b) == "Visa" assert b is PaymentCardBrand.visa assert b == PaymentCardBrand.visa assert b in {PaymentCardBrand.visa, PaymentCardBrand.mastercard} - b = 'Visa' + b = "Visa" assert b is not PaymentCardBrand.visa assert b == PaymentCardBrand.visa assert b in {PaymentCardBrand.visa, PaymentCardBrand.mastercard} diff --git a/tests/test_ulid.py b/tests/test_ulid.py index b9ae527a..e2fb70af 100644 --- a/tests/test_ulid.py +++ b/tests/test_ulid.py @@ -3,7 +3,6 @@ import pytest from pydantic import BaseModel, ValidationError - from pydantic_extra_types.ulid import ULID try: @@ -19,62 +18,62 @@ class Something(BaseModel): @pytest.mark.parametrize( - 'ulid, result, valid', + "ulid, result, valid", [ # Valid ULID for str format - ('01BTGNYV6HRNK8K8VKZASZCFPE', '01BTGNYV6HRNK8K8VKZASZCFPE', True), - ('01BTGNYV6HRNK8K8VKZASZCFPF', '01BTGNYV6HRNK8K8VKZASZCFPF', True), + ("01BTGNYV6HRNK8K8VKZASZCFPE", "01BTGNYV6HRNK8K8VKZASZCFPE", True), + ("01BTGNYV6HRNK8K8VKZASZCFPF", "01BTGNYV6HRNK8K8VKZASZCFPF", True), # Invalid ULID for str format - ('01BTGNYV6HRNK8K8VKZASZCFP', None, False), # Invalid ULID (short length) - ('01BTGNYV6HRNK8K8VKZASZCFPEA', None, False), # Invalid ULID (long length) + ("01BTGNYV6HRNK8K8VKZASZCFP", None, False), # Invalid ULID (short length) + ("01BTGNYV6HRNK8K8VKZASZCFPEA", None, False), # Invalid ULID (long length) # Valid ULID for _ULID format - (_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPE'), '01BTGNYV6HRNK8K8VKZASZCFPE', True), - (_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPF'), '01BTGNYV6HRNK8K8VKZASZCFPF', True), + (_ULID.from_str("01BTGNYV6HRNK8K8VKZASZCFPE"), "01BTGNYV6HRNK8K8VKZASZCFPE", True), + (_ULID.from_str("01BTGNYV6HRNK8K8VKZASZCFPF"), "01BTGNYV6HRNK8K8VKZASZCFPF", True), # Invalid _ULID for bytes format - (b'\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8', None, False), # Invalid ULID (short length) - (b'\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8\xB6\x00', None, False), # Invalid ULID (long length) + (b"\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8", None, False), # Invalid ULID (short length) + (b"\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8\xB6\x00", None, False), # Invalid ULID (long length) # Valid ULID for int format - (109667145845879622871206540411193812282, '2JG4FVY7N8XS4GFVHPXGJZ8S9T', True), - (109667145845879622871206540411193812283, '2JG4FVY7N8XS4GFVHPXGJZ8S9V', True), - (109667145845879622871206540411193812284, '2JG4FVY7N8XS4GFVHPXGJZ8S9W', True), + (109667145845879622871206540411193812282, "2JG4FVY7N8XS4GFVHPXGJZ8S9T", True), + (109667145845879622871206540411193812283, "2JG4FVY7N8XS4GFVHPXGJZ8S9V", True), + (109667145845879622871206540411193812284, "2JG4FVY7N8XS4GFVHPXGJZ8S9W", True), ], ) def test_format_for_ulid(ulid: Any, result: Any, valid: bool): if valid: assert str(Something(ulid=ulid).ulid) == result else: - with pytest.raises(ValidationError, match='format'): + with pytest.raises(ValidationError, match="format"): Something(ulid=ulid) def test_property_for_ulid(): - ulid = Something(ulid='01BTGNYV6HRNK8K8VKZASZCFPE').ulid - assert ulid.hex == '015ea15f6cd1c56689a373fab3f63ece' - assert ulid == '01BTGNYV6HRNK8K8VKZASZCFPE' + ulid = Something(ulid="01BTGNYV6HRNK8K8VKZASZCFPE").ulid + assert ulid.hex == "015ea15f6cd1c56689a373fab3f63ece" + assert ulid == "01BTGNYV6HRNK8K8VKZASZCFPE" assert ulid.datetime == datetime(2017, 9, 20, 22, 18, 59, 153000, tzinfo=timezone.utc) assert ulid.timestamp == 1505945939.153 def test_json_schema(): - assert Something.model_json_schema(mode='validation') == { - 'properties': { - 'ulid': { - 'anyOf': [{'type': 'integer'}, {'format': 'binary', 'type': 'string'}, {'type': 'string'}], - 'title': 'Ulid', + assert Something.model_json_schema(mode="validation") == { + "properties": { + "ulid": { + "anyOf": [{"type": "integer"}, {"format": "binary", "type": "string"}, {"type": "string"}], + "title": "Ulid", } }, - 'required': ['ulid'], - 'title': 'Something', - 'type': 'object', + "required": ["ulid"], + "title": "Something", + "type": "object", } - assert Something.model_json_schema(mode='serialization') == { - 'properties': { - 'ulid': { - 'anyOf': [{'type': 'integer'}, {'format': 'binary', 'type': 'string'}, {'type': 'string'}], - 'title': 'Ulid', + assert Something.model_json_schema(mode="serialization") == { + "properties": { + "ulid": { + "anyOf": [{"type": "integer"}, {"format": "binary", "type": "string"}, {"type": "string"}], + "title": "Ulid", } }, - 'required': ['ulid'], - 'title': 'Something', - 'type': 'object', + "required": ["ulid"], + "title": "Something", + "type": "object", }