diff --git a/doc/source/conf.py b/doc/source/conf.py index 24482d7c7461a..6671cefae9073 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -583,7 +583,14 @@ class AccessorCallableDocumenter(AccessorLevelDocumenter, MethodDocumenter): priority = 0.5 def format_name(self): - return MethodDocumenter.format_name(self).rstrip(".__call__") + if sys.version_info < (3, 9): + # NOTE pyupgrade will remove this when we run it with --py39-plus + # so don't remove the unnecessary `else` statement below + from pandas.util._str_methods import removesuffix + + return removesuffix(MethodDocumenter.format_name(self), ".__call__") + else: + return MethodDocumenter.format_name(self).removesuffix(".__call__") class PandasAutosummary(Autosummary): diff --git a/pandas/core/ops/common.py b/pandas/core/ops/common.py index f0e6aa3750cee..8bba400cedc9a 100644 --- a/pandas/core/ops/common.py +++ b/pandas/core/ops/common.py @@ -4,6 +4,7 @@ from __future__ import annotations from functools import wraps +import sys from typing import Callable from pandas._libs.lib import item_from_zerodim @@ -52,7 +53,16 @@ def _unpack_zerodim_and_defer(method, name: str): ------- method """ - is_cmp = name.strip("__") in {"eq", "ne", "lt", "le", "gt", "ge"} + if sys.version_info < (3, 9): + from pandas.util._str_methods import ( + removeprefix, + removesuffix, + ) + + stripped_name = removesuffix(removeprefix(name, "__"), "__") + else: + stripped_name = name.removeprefix("__").removesuffix("__") + is_cmp = stripped_name in {"eq", "ne", "lt", "le", "gt", "ge"} @wraps(method) def new_method(self, other): diff --git a/pandas/core/strings/object_array.py b/pandas/core/strings/object_array.py index 21e7ede3ed386..2d77cd0da816f 100644 --- a/pandas/core/strings/object_array.py +++ b/pandas/core/strings/object_array.py @@ -3,6 +3,7 @@ from collections.abc import Callable # noqa: PDF001 import functools import re +import sys import textwrap from typing import ( TYPE_CHECKING, @@ -462,16 +463,14 @@ def removeprefix(text: str) -> str: return self._str_map(removeprefix) def _str_removesuffix(self, suffix: str) -> Series: - # this could be used on Python 3.9+ - # f = lambda x: x.removesuffix(suffix) - # return self._str_map(str.removesuffix) + if sys.version_info < (3, 9): + # NOTE pyupgrade will remove this when we run it with --py39-plus + # so don't remove the unnecessary `else` statement below + from pandas.util._str_methods import removesuffix - def removesuffix(text: str) -> str: - if text.endswith(suffix): - return text[: -len(suffix)] - return text - - return self._str_map(removesuffix) + return self._str_map(functools.partial(removesuffix, suffix=suffix)) + else: + return self._str_map(lambda x: x.removesuffix(suffix)) def _str_extract(self, pat: str, flags: int = 0, expand: bool = True): regex = re.compile(pat, flags=flags) diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index f321ecc2f65ff..cb1bcf7a813eb 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -1587,7 +1587,7 @@ def test_get_schema2(self, test_frame1): def _get_sqlite_column_type(self, schema, column): for col in schema.split("\n"): - if col.split()[0].strip('""') == column: + if col.split()[0].strip('"') == column: return col.split()[1] raise ValueError(f"Column {column} not found") diff --git a/pandas/tests/util/test_str_methods.py b/pandas/tests/util/test_str_methods.py new file mode 100644 index 0000000000000..c07730f589824 --- /dev/null +++ b/pandas/tests/util/test_str_methods.py @@ -0,0 +1,47 @@ +import sys + +import pytest + +if sys.version_info < (3, 9): + from pandas.util._str_methods import ( + removeprefix, + removesuffix, + ) + + @pytest.mark.parametrize( + "string, prefix, expected", + ( + ("wildcat", "wild", "cat"), + ("blackbird", "black", "bird"), + ("housefly", "house", "fly"), + ("ladybug", "lady", "bug"), + ("rattlesnake", "rattle", "snake"), + ("baboon", "badger", "baboon"), + ("quetzal", "elk", "quetzal"), + ), + ) + def test_remove_prefix(string, prefix, expected): + result = removeprefix(string, prefix) + assert result == expected + + @pytest.mark.parametrize( + "string, suffix, expected", + ( + ("wildcat", "cat", "wild"), + ("blackbird", "bird", "black"), + ("housefly", "fly", "house"), + ("ladybug", "bug", "lady"), + ("rattlesnake", "snake", "rattle"), + ("seahorse", "horse", "sea"), + ("baboon", "badger", "baboon"), + ("quetzal", "elk", "quetzal"), + ), + ) + def test_remove_suffix(string, suffix, expected): + result = removesuffix(string, suffix) + assert result == expected + +else: + # NOTE: remove this file when pyupgrade --py39-plus removes + # the above block + pass diff --git a/pandas/util/_str_methods.py b/pandas/util/_str_methods.py new file mode 100644 index 0000000000000..8f7aef80bc108 --- /dev/null +++ b/pandas/util/_str_methods.py @@ -0,0 +1,28 @@ +""" +Python3.9 introduces removesuffix and remove prefix. + +They're reimplemented here for use in Python3.8. + +NOTE: when pyupgrade --py39-plus removes nearly everything in this file, +this file and the associated tests should be removed. +""" +from __future__ import annotations + +import sys + +if sys.version_info < (3, 9): + + def removesuffix(string: str, suffix: str) -> str: + if string.endswith(suffix): + return string[: -len(suffix)] + return string + + def removeprefix(string: str, prefix: str) -> str: + if string.startswith(prefix): + return string[len(prefix) :] + return string + +else: + # NOTE: remove this file when pyupgrade --py39-plus removes + # the above block + pass diff --git a/setup.cfg b/setup.cfg index 62bebac8a2885..98c5a27d3e6a0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -194,8 +194,6 @@ ignore = # found modulo formatter (incorrect picks up mod operations) S001, # controversial - B005, - # controversial B006, # controversial B007,