From 12db3c207356af903e1ceeba990c994ed0106092 Mon Sep 17 00:00:00 2001 From: LincolnPuzey <18750802+LincolnPuzey@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:03:37 +0800 Subject: [PATCH] Vendor stringcase library (#1727) - fixes #1655 Commits: - Vendor `stringcase` library - Fix deprecation warning in it - Swap so it is used, remove dependency on actual library --------- Co-authored-by: Pierre Camilleri --- frictionless/helpers/general.py | 2 +- frictionless/metadata/metadata.py | 13 +++- frictionless/vendors/stringcase.py | 101 +++++++++++++++++++++++++++++ pyproject.toml | 1 - 4 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 frictionless/vendors/stringcase.py diff --git a/frictionless/helpers/general.py b/frictionless/helpers/general.py index 45e27ce5b7..105e992c31 100644 --- a/frictionless/helpers/general.py +++ b/frictionless/helpers/general.py @@ -17,7 +17,7 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, TypeVar, Union from urllib.parse import parse_qs, urlparse -import stringcase # type: ignore +from ..vendors import stringcase # General diff --git a/frictionless/metadata/metadata.py b/frictionless/metadata/metadata.py index dbf9e572df..ac8c2e5d82 100644 --- a/frictionless/metadata/metadata.py +++ b/frictionless/metadata/metadata.py @@ -21,12 +21,12 @@ Union, ) -import stringcase # type: ignore from typing_extensions import Self from .. import helpers from ..exception import FrictionlessException from ..platform import platform +from ..vendors import stringcase if TYPE_CHECKING: from .. import types @@ -136,7 +136,10 @@ def set_not_defined(self, name: str, value: Any, *, distinct: bool = False) -> N @classmethod def validate_descriptor( - cls, descriptor: Union[types.IDescriptor, str], *, basepath: Optional[str] = None + cls, + descriptor: Union[types.IDescriptor, str], + *, + basepath: Optional[str] = None, ) -> Report: errors = [] timer = helpers.Timer() @@ -391,7 +394,11 @@ def metadata_validate( @classmethod def metadata_import( - cls, descriptor: types.IDescriptor, *, with_basepath: bool = False, **options: Any + cls, + descriptor: types.IDescriptor, + *, + with_basepath: bool = False, + **options: Any, ) -> Self: merged_options = {} profile = cls.metadata_ensure_profile() diff --git a/frictionless/vendors/stringcase.py b/frictionless/vendors/stringcase.py new file mode 100644 index 0000000000..c41da38d45 --- /dev/null +++ b/frictionless/vendors/stringcase.py @@ -0,0 +1,101 @@ +""" +String convert functions + +The MIT License (MIT) + +Copyright (c) 2015 Taka Okunishi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# The "stringcase" package appears to unmaintained +# https://github.com/okunishinishi/python-stringcase/issues/42 +# Relevant parts have been vendored here so we can fix warnings +# that will eventually become errors +# Code here was copied from the 1.2.0 version uploaded to PyPI +# https://pypi.org/project/stringcase/1.2.0/ + +import re + + +def camelcase(string: str): + """Convert string into camel case. + + Args: + string: String to convert. + + Returns: + string: Camel case string. + + """ + + string = re.sub(r"\w[\s\W]+\w", "", str(string)) + if not string: + return string + return lowercase(string[0]) + re.sub( + r"[\-_\.\s]([a-z])", lambda matched: uppercase(matched.group(1)), string[1:] + ) + + +def lowercase(string: str): + """Convert string into lower case. + + Args: + string: String to convert. + + Returns: + string: Lowercase case string. + + """ + + return str(string).lower() + + +def snakecase(string: str): + """Convert string into snake case. + Join punctuation with underscore + + Args: + string: String to convert. + + Returns: + string: Snake cased string. + + """ + + string = re.sub(r"[\-\.\s]", "_", str(string)) + if not string: + return string + return lowercase(string[0]) + re.sub( + r"[A-Z]", lambda matched: "_" + lowercase(matched.group(0)), string[1:] + ) + + +def uppercase(string: str): + """Convert string into upper case. + + Args: + string: String to convert. + + Returns: + string: Uppercase case string. + + """ + + return str(string).upper() diff --git a/pyproject.toml b/pyproject.toml index fbe068da06..82b131cbcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ dependencies = [ "tabulate>=0.8.10", "jsonschema>=4.20", "simpleeval>=0.9.11", - "stringcase>=1.2", "typer>=0.12", "validators>=0.18", "python-slugify>=1.2",