Repository: modin-project/modin

Issue:
Implement `Series.str` through `CachedAccessor` as it done for `Series.sparse`



Discussion:

Patch:
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -39,6 +39,7 @@
 from .iterator import PartitionIterator
 from .utils import from_pandas, is_scalar, _doc_binary_op, cast_function_modin2pandas
 from .accessor import CachedAccessor, SparseAccessor
+from .series_utils import StringMethods
 
 
 if TYPE_CHECKING:
@@ -1836,6 +1837,7 @@ def sort_values(
         )
 
     sparse = CachedAccessor("sparse", SparseAccessor)
+    str = CachedAccessor("str", StringMethods)
 
     def squeeze(self, axis=None):  # noqa: PR01, RT01, D200
         """
@@ -2257,15 +2259,6 @@ def shape(self):  # noqa: RT01, D200
         """
         return (len(self),)
 
-    @property
-    def str(self):  # noqa: RT01, D200
-        """
-        Vectorized string functions for Series and Index.
-        """
-        from .series_utils import StringMethods
-
-        return StringMethods(self)
-
     def _to_pandas(self):
         """
         Convert Modin Series to pandas Series.
diff --git a/modin/pandas/series_utils.py b/modin/pandas/series_utils.py
--- a/modin/pandas/series_utils.py
+++ b/modin/pandas/series_utils.py
@@ -22,7 +22,6 @@
 import pandas
 from modin.logging import ClassLogger
 from modin.utils import _inherit_docstrings
-from .series import Series
 
 if sys.version_info[0] == 3 and sys.version_info[1] >= 7:
     # Python >= 3.7
@@ -42,6 +41,13 @@ def __init__(self, series):
         self._series = series
         self._query_compiler = series._query_compiler
 
+    @pandas.util.cache_readonly
+    def _Series(self):  # noqa: GL08
+        # to avoid cyclic import
+        from .series import Series
+
+        return Series
+
     @property
     def categories(self):
         return self._series.dtype.categories
@@ -59,7 +65,7 @@ def ordered(self):
 
     @property
     def codes(self):
-        return Series(query_compiler=self._query_compiler.cat_codes())
+        return self._Series(query_compiler=self._query_compiler.cat_codes())
 
     def rename_categories(self, new_categories, inplace=False):
         return self._default_to_pandas(
@@ -129,17 +135,24 @@ def _default_to_pandas(self, op, *args, **kwargs):
 
 @_inherit_docstrings(pandas.core.strings.StringMethods)
 class StringMethods(ClassLogger):
-    def __init__(self, series):
+    def __init__(self, data):
         # Check if dtypes is objects
 
-        self._series = series
-        self._query_compiler = series._query_compiler
+        self._series = data
+        self._query_compiler = data._query_compiler
+
+    @pandas.util.cache_readonly
+    def _Series(self):  # noqa: GL08
+        # to avoid cyclic import
+        from .series import Series
+
+        return Series
 
     def casefold(self):
         return self._default_to_pandas(pandas.Series.str.casefold)
 
     def cat(self, others=None, sep=None, na_rep=None, join=None):
-        if isinstance(others, Series):
+        if isinstance(others, self._Series):
             others = others._to_pandas()
         return self._default_to_pandas(
             pandas.Series.str.cat, others=others, sep=sep, na_rep=na_rep, join=join
@@ -159,7 +172,7 @@ def split(self, pat=None, n=-1, expand=False):
                 pandas.Series.str.split, pat=pat, n=n, expand=expand
             )
         else:
-            return Series(
+            return self._Series(
                 query_compiler=self._query_compiler.str_split(
                     pat=pat, n=n, expand=expand
                 )
@@ -174,19 +187,19 @@ def rsplit(self, pat=None, n=-1, expand=False):
                 pandas.Series.str.rsplit, pat=pat, n=n, expand=expand
             )
         else:
-            return Series(
+            return self._Series(
                 query_compiler=self._query_compiler.str_rsplit(
                     pat=pat, n=n, expand=expand
                 )
             )
 
     def get(self, i):
-        return Series(query_compiler=self._query_compiler.str_get(i))
+        return self._Series(query_compiler=self._query_compiler.str_get(i))
 
     def join(self, sep):
         if sep is None:
             raise AttributeError("'NoneType' object has no attribute 'join'")
-        return Series(query_compiler=self._query_compiler.str_join(sep))
+        return self._Series(query_compiler=self._query_compiler.str_join(sep))
 
     def get_dummies(self, sep="|"):
         return self._default_to_pandas(pandas.Series.str.get_dummies, sep=sep)
@@ -194,7 +207,7 @@ def get_dummies(self, sep="|"):
     def contains(self, pat, case=True, flags=0, na=np.NaN, regex=True):
         if pat is None and not case:
             raise AttributeError("'NoneType' object has no attribute 'upper'")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_contains(
                 pat, case=case, flags=flags, na=na, regex=regex
             )
@@ -203,7 +216,7 @@ def contains(self, pat, case=True, flags=0, na=np.NaN, regex=True):
     def replace(self, pat, repl, n=-1, case=None, flags=0, regex=True):
         if not (isinstance(repl, str) or callable(repl)):
             raise TypeError("repl must be a string or callable")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_replace(
                 pat, repl, n=n, case=case, flags=flags, regex=regex
             )
@@ -212,7 +225,7 @@ def replace(self, pat, repl, n=-1, case=None, flags=0, regex=True):
     def pad(self, width, side="left", fillchar=" "):
         if len(fillchar) != 1:
             raise TypeError("fillchar must be a character, not str")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_pad(
                 width, side=side, fillchar=fillchar
             )
@@ -221,43 +234,45 @@ def pad(self, width, side="left", fillchar=" "):
     def center(self, width, fillchar=" "):
         if len(fillchar) != 1:
             raise TypeError("fillchar must be a character, not str")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_center(width, fillchar=fillchar)
         )
 
     def ljust(self, width, fillchar=" "):
         if len(fillchar) != 1:
             raise TypeError("fillchar must be a character, not str")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_ljust(width, fillchar=fillchar)
         )
 
     def rjust(self, width, fillchar=" "):
         if len(fillchar) != 1:
             raise TypeError("fillchar must be a character, not str")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_rjust(width, fillchar=fillchar)
         )
 
     def zfill(self, width):
-        return Series(query_compiler=self._query_compiler.str_zfill(width))
+        return self._Series(query_compiler=self._query_compiler.str_zfill(width))
 
     def wrap(self, width, **kwargs):
         if width <= 0:
             raise ValueError("invalid width {} (must be > 0)".format(width))
-        return Series(query_compiler=self._query_compiler.str_wrap(width, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.str_wrap(width, **kwargs)
+        )
 
     def slice(self, start=None, stop=None, step=None):
         if step == 0:
             raise ValueError("slice step cannot be zero")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_slice(
                 start=start, stop=stop, step=step
             )
         )
 
     def slice_replace(self, start=None, stop=None, repl=None):
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_slice_replace(
                 start=start, stop=stop, repl=repl
             )
@@ -266,12 +281,14 @@ def slice_replace(self, start=None, stop=None, repl=None):
     def count(self, pat, flags=0, **kwargs):
         if not isinstance(pat, (str, _pattern_type)):
             raise TypeError("first argument must be string or compiled pattern")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_count(pat, flags=flags, **kwargs)
         )
 
     def startswith(self, pat, na=np.NaN):
-        return Series(query_compiler=self._query_compiler.str_startswith(pat, na=na))
+        return self._Series(
+            query_compiler=self._query_compiler.str_startswith(pat, na=na)
+        )
 
     def encode(self, encoding, errors="strict"):
         return self._default_to_pandas(
@@ -279,19 +296,21 @@ def encode(self, encoding, errors="strict"):
         )
 
     def endswith(self, pat, na=np.NaN):
-        return Series(query_compiler=self._query_compiler.str_endswith(pat, na=na))
+        return self._Series(
+            query_compiler=self._query_compiler.str_endswith(pat, na=na)
+        )
 
     def findall(self, pat, flags=0, **kwargs):
         if not isinstance(pat, (str, _pattern_type)):
             raise TypeError("first argument must be string or compiled pattern")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_findall(pat, flags=flags, **kwargs)
         )
 
     def fullmatch(self, pat, case=True, flags=0, na=None):
         if not isinstance(pat, (str, _pattern_type)):
             raise TypeError("first argument must be string or compiled pattern")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_fullmatch(
                 pat, case=case, flags=flags, na=na
             )
@@ -300,7 +319,7 @@ def fullmatch(self, pat, case=True, flags=0, na=None):
     def match(self, pat, case=True, flags=0, na=np.NaN):
         if not isinstance(pat, (str, _pattern_type)):
             raise TypeError("first argument must be string or compiled pattern")
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_match(pat, flags=flags, na=na)
         )
 
@@ -321,16 +340,22 @@ def extractall(self, pat, flags=0):
         return self._default_to_pandas(pandas.Series.str.extractall, pat, flags=flags)
 
     def len(self):
-        return Series(query_compiler=self._query_compiler.str_len())
+        return self._Series(query_compiler=self._query_compiler.str_len())
 
     def strip(self, to_strip=None):
-        return Series(query_compiler=self._query_compiler.str_strip(to_strip=to_strip))
+        return self._Series(
+            query_compiler=self._query_compiler.str_strip(to_strip=to_strip)
+        )
 
     def rstrip(self, to_strip=None):
-        return Series(query_compiler=self._query_compiler.str_rstrip(to_strip=to_strip))
+        return self._Series(
+            query_compiler=self._query_compiler.str_rstrip(to_strip=to_strip)
+        )
 
     def lstrip(self, to_strip=None):
-        return Series(query_compiler=self._query_compiler.str_lstrip(to_strip=to_strip))
+        return self._Series(
+            query_compiler=self._query_compiler.str_lstrip(to_strip=to_strip)
+        )
 
     def partition(self, sep=" ", expand=True):
         if sep is not None and len(sep) == 0:
@@ -341,20 +366,24 @@ def partition(self, sep=" ", expand=True):
                 pandas.Series.str.partition, sep=sep, expand=expand
             )
         else:
-            return Series(
+            return self._Series(
                 query_compiler=self._query_compiler.str_partition(
                     sep=sep, expand=expand
                 )
             )
 
     def removeprefix(self, prefix):
-        return Series(query_compiler=self._query_compiler.str_removeprefix(prefix))
+        return self._Series(
+            query_compiler=self._query_compiler.str_removeprefix(prefix)
+        )
 
     def removesuffix(self, suffix):
-        return Series(query_compiler=self._query_compiler.str_removesuffix(suffix))
+        return self._Series(
+            query_compiler=self._query_compiler.str_removesuffix(suffix)
+        )
 
     def repeat(self, repeats):
-        return Series(query_compiler=self._query_compiler.str_repeat(repeats))
+        return self._Series(query_compiler=self._query_compiler.str_repeat(repeats))
 
     def rpartition(self, sep=" ", expand=True):
         if sep is not None and len(sep) == 0:
@@ -365,27 +394,27 @@ def rpartition(self, sep=" ", expand=True):
                 pandas.Series.str.rpartition, sep=sep, expand=expand
             )
         else:
-            return Series(
+            return self._Series(
                 query_compiler=self._query_compiler.str_rpartition(
                     sep=sep, expand=expand
                 )
             )
 
     def lower(self):
-        return Series(query_compiler=self._query_compiler.str_lower())
+        return self._Series(query_compiler=self._query_compiler.str_lower())
 
     def upper(self):
-        return Series(query_compiler=self._query_compiler.str_upper())
+        return self._Series(query_compiler=self._query_compiler.str_upper())
 
     def title(self):
-        return Series(query_compiler=self._query_compiler.str_title())
+        return self._Series(query_compiler=self._query_compiler.str_title())
 
     def find(self, sub, start=0, end=None):
         if not isinstance(sub, str):
             raise TypeError(
                 "expected a string object, not {0}".format(type(sub).__name__)
             )
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_find(sub, start=start, end=end)
         )
 
@@ -394,7 +423,7 @@ def rfind(self, sub, start=0, end=None):
             raise TypeError(
                 "expected a string object, not {0}".format(type(sub).__name__)
             )
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_rfind(sub, start=start, end=end)
         )
 
@@ -403,7 +432,7 @@ def index(self, sub, start=0, end=None):
             raise TypeError(
                 "expected a string object, not {0}".format(type(sub).__name__)
             )
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_index(sub, start=start, end=end)
         )
 
@@ -412,51 +441,51 @@ def rindex(self, sub, start=0, end=None):
             raise TypeError(
                 "expected a string object, not {0}".format(type(sub).__name__)
             )
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.str_rindex(sub, start=start, end=end)
         )
 
     def capitalize(self):
-        return Series(query_compiler=self._query_compiler.str_capitalize())
+        return self._Series(query_compiler=self._query_compiler.str_capitalize())
 
     def swapcase(self):
-        return Series(query_compiler=self._query_compiler.str_swapcase())
+        return self._Series(query_compiler=self._query_compiler.str_swapcase())
 
     def normalize(self, form):
-        return Series(query_compiler=self._query_compiler.str_normalize(form))
+        return self._Series(query_compiler=self._query_compiler.str_normalize(form))
 
     def translate(self, table):
-        return Series(query_compiler=self._query_compiler.str_translate(table))
+        return self._Series(query_compiler=self._query_compiler.str_translate(table))
 
     def isalnum(self):
-        return Series(query_compiler=self._query_compiler.str_isalnum())
+        return self._Series(query_compiler=self._query_compiler.str_isalnum())
 
     def isalpha(self):
-        return Series(query_compiler=self._query_compiler.str_isalpha())
+        return self._Series(query_compiler=self._query_compiler.str_isalpha())
 
     def isdigit(self):
-        return Series(query_compiler=self._query_compiler.str_isdigit())
+        return self._Series(query_compiler=self._query_compiler.str_isdigit())
 
     def isspace(self):
-        return Series(query_compiler=self._query_compiler.str_isspace())
+        return self._Series(query_compiler=self._query_compiler.str_isspace())
 
     def islower(self):
-        return Series(query_compiler=self._query_compiler.str_islower())
+        return self._Series(query_compiler=self._query_compiler.str_islower())
 
     def isupper(self):
-        return Series(query_compiler=self._query_compiler.str_isupper())
+        return self._Series(query_compiler=self._query_compiler.str_isupper())
 
     def istitle(self):
-        return Series(query_compiler=self._query_compiler.str_istitle())
+        return self._Series(query_compiler=self._query_compiler.str_istitle())
 
     def isnumeric(self):
-        return Series(query_compiler=self._query_compiler.str_isnumeric())
+        return self._Series(query_compiler=self._query_compiler.str_isnumeric())
 
     def isdecimal(self):
-        return Series(query_compiler=self._query_compiler.str_isdecimal())
+        return self._Series(query_compiler=self._query_compiler.str_isdecimal())
 
     def __getitem__(self, key):
-        return Series(query_compiler=self._query_compiler.str___getitem__(key))
+        return self._Series(query_compiler=self._query_compiler.str___getitem__(key))
 
     def _default_to_pandas(self, op, *args, **kwargs):
         """
@@ -487,113 +516,120 @@ def __init__(self, series):
         self._series = series
         self._query_compiler = series._query_compiler
 
+    @pandas.util.cache_readonly
+    def _Series(self):  # noqa: GL08
+        # to avoid cyclic import
+        from .series import Series
+
+        return Series
+
     @property
     def date(self):
-        return Series(query_compiler=self._query_compiler.dt_date())
+        return self._Series(query_compiler=self._query_compiler.dt_date())
 
     @property
     def time(self):
-        return Series(query_compiler=self._query_compiler.dt_time())
+        return self._Series(query_compiler=self._query_compiler.dt_time())
 
     @property
     def timetz(self):
-        return Series(query_compiler=self._query_compiler.dt_timetz())
+        return self._Series(query_compiler=self._query_compiler.dt_timetz())
 
     @property
     def year(self):
-        return Series(query_compiler=self._query_compiler.dt_year())
+        return self._Series(query_compiler=self._query_compiler.dt_year())
 
     @property
     def month(self):
-        return Series(query_compiler=self._query_compiler.dt_month())
+        return self._Series(query_compiler=self._query_compiler.dt_month())
 
     @property
     def day(self):
-        return Series(query_compiler=self._query_compiler.dt_day())
+        return self._Series(query_compiler=self._query_compiler.dt_day())
 
     @property
     def hour(self):
-        return Series(query_compiler=self._query_compiler.dt_hour())
+        return self._Series(query_compiler=self._query_compiler.dt_hour())
 
     @property
     def minute(self):
-        return Series(query_compiler=self._query_compiler.dt_minute())
+        return self._Series(query_compiler=self._query_compiler.dt_minute())
 
     @property
     def second(self):
-        return Series(query_compiler=self._query_compiler.dt_second())
+        return self._Series(query_compiler=self._query_compiler.dt_second())
 
     @property
     def microsecond(self):
-        return Series(query_compiler=self._query_compiler.dt_microsecond())
+        return self._Series(query_compiler=self._query_compiler.dt_microsecond())
 
     @property
     def nanosecond(self):
-        return Series(query_compiler=self._query_compiler.dt_nanosecond())
+        return self._Series(query_compiler=self._query_compiler.dt_nanosecond())
 
     @property
     def week(self):
-        return Series(query_compiler=self._query_compiler.dt_week())
+        return self._Series(query_compiler=self._query_compiler.dt_week())
 
     @property
     def weekofyear(self):
-        return Series(query_compiler=self._query_compiler.dt_weekofyear())
+        return self._Series(query_compiler=self._query_compiler.dt_weekofyear())
 
     @property
     def dayofweek(self):
-        return Series(query_compiler=self._query_compiler.dt_dayofweek())
+        return self._Series(query_compiler=self._query_compiler.dt_dayofweek())
 
     day_of_week = dayofweek
 
     @property
     def weekday(self):
-        return Series(query_compiler=self._query_compiler.dt_weekday())
+        return self._Series(query_compiler=self._query_compiler.dt_weekday())
 
     @property
     def dayofyear(self):
-        return Series(query_compiler=self._query_compiler.dt_dayofyear())
+        return self._Series(query_compiler=self._query_compiler.dt_dayofyear())
 
     day_of_year = dayofyear
 
     @property
     def quarter(self):
-        return Series(query_compiler=self._query_compiler.dt_quarter())
+        return self._Series(query_compiler=self._query_compiler.dt_quarter())
 
     @property
     def is_month_start(self):
-        return Series(query_compiler=self._query_compiler.dt_is_month_start())
+        return self._Series(query_compiler=self._query_compiler.dt_is_month_start())
 
     @property
     def is_month_end(self):
-        return Series(query_compiler=self._query_compiler.dt_is_month_end())
+        return self._Series(query_compiler=self._query_compiler.dt_is_month_end())
 
     @property
     def is_quarter_start(self):
-        return Series(query_compiler=self._query_compiler.dt_is_quarter_start())
+        return self._Series(query_compiler=self._query_compiler.dt_is_quarter_start())
 
     @property
     def is_quarter_end(self):
-        return Series(query_compiler=self._query_compiler.dt_is_quarter_end())
+        return self._Series(query_compiler=self._query_compiler.dt_is_quarter_end())
 
     @property
     def is_year_start(self):
-        return Series(query_compiler=self._query_compiler.dt_is_year_start())
+        return self._Series(query_compiler=self._query_compiler.dt_is_year_start())
 
     @property
     def is_year_end(self):
-        return Series(query_compiler=self._query_compiler.dt_is_year_end())
+        return self._Series(query_compiler=self._query_compiler.dt_is_year_end())
 
     @property
     def is_leap_year(self):
-        return Series(query_compiler=self._query_compiler.dt_is_leap_year())
+        return self._Series(query_compiler=self._query_compiler.dt_is_leap_year())
 
     @property
     def daysinmonth(self):
-        return Series(query_compiler=self._query_compiler.dt_daysinmonth())
+        return self._Series(query_compiler=self._query_compiler.dt_daysinmonth())
 
     @property
     def days_in_month(self):
-        return Series(query_compiler=self._query_compiler.dt_days_in_month())
+        return self._Series(query_compiler=self._query_compiler.dt_days_in_month())
 
     @property
     def tz(self) -> "tzinfo | None":
@@ -607,49 +643,67 @@ def freq(self):
         return self._query_compiler.dt_freq().to_pandas().squeeze()
 
     def to_period(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_to_period(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_to_period(*args, **kwargs)
+        )
 
     def asfreq(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_asfreq(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_asfreq(*args, **kwargs)
+        )
 
     def to_pydatetime(self):
-        return Series(query_compiler=self._query_compiler.dt_to_pydatetime()).to_numpy()
+        return self._Series(
+            query_compiler=self._query_compiler.dt_to_pydatetime()
+        ).to_numpy()
 
     def tz_localize(self, *args, **kwargs):
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.dt_tz_localize(*args, **kwargs)
         )
 
     def tz_convert(self, *args, **kwargs):
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.dt_tz_convert(*args, **kwargs)
         )
 
     def normalize(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_normalize(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_normalize(*args, **kwargs)
+        )
 
     def strftime(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_strftime(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_strftime(*args, **kwargs)
+        )
 
     def round(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_round(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_round(*args, **kwargs)
+        )
 
     def floor(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_floor(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_floor(*args, **kwargs)
+        )
 
     def ceil(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_ceil(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_ceil(*args, **kwargs)
+        )
 
     def month_name(self, *args, **kwargs):
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.dt_month_name(*args, **kwargs)
         )
 
     def day_name(self, *args, **kwargs):
-        return Series(query_compiler=self._query_compiler.dt_day_name(*args, **kwargs))
+        return self._Series(
+            query_compiler=self._query_compiler.dt_day_name(*args, **kwargs)
+        )
 
     def total_seconds(self, *args, **kwargs):
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.dt_total_seconds(*args, **kwargs)
         )
 
@@ -659,19 +713,19 @@ def to_pytimedelta(self) -> "npt.NDArray[np.object_]":
 
     @property
     def seconds(self):
-        return Series(query_compiler=self._query_compiler.dt_seconds())
+        return self._Series(query_compiler=self._query_compiler.dt_seconds())
 
     @property
     def days(self):
-        return Series(query_compiler=self._query_compiler.dt_days())
+        return self._Series(query_compiler=self._query_compiler.dt_days())
 
     @property
     def microseconds(self):
-        return Series(query_compiler=self._query_compiler.dt_microseconds())
+        return self._Series(query_compiler=self._query_compiler.dt_microseconds())
 
     @property
     def nanoseconds(self):
-        return Series(query_compiler=self._query_compiler.dt_nanoseconds())
+        return self._Series(query_compiler=self._query_compiler.dt_nanoseconds())
 
     @property
     def components(self):
@@ -686,17 +740,17 @@ def isocalendar(self):
 
     @property
     def qyear(self):
-        return Series(query_compiler=self._query_compiler.dt_qyear())
+        return self._Series(query_compiler=self._query_compiler.dt_qyear())
 
     @property
     def start_time(self):
-        return Series(query_compiler=self._query_compiler.dt_start_time())
+        return self._Series(query_compiler=self._query_compiler.dt_start_time())
 
     @property
     def end_time(self):
-        return Series(query_compiler=self._query_compiler.dt_end_time())
+        return self._Series(query_compiler=self._query_compiler.dt_end_time())
 
     def to_timestamp(self, *args, **kwargs):
-        return Series(
+        return self._Series(
             query_compiler=self._query_compiler.dt_to_timestamp(*args, **kwargs)
         )


Test patch:
diff --git a/modin/pandas/test/test_api.py b/modin/pandas/test/test_api.py
--- a/modin/pandas/test/test_api.py
+++ b/modin/pandas/test/test_api.py
@@ -59,6 +59,7 @@ def test_top_level_api_equality():
         "datetime",
         "warnings",
         "os",
+        "series_utils",
     ]
 
     assert not len(
@@ -237,7 +238,7 @@ def test_dataframe_api_equality():
 
 
 def test_series_str_api_equality():
-    modin_dir = [obj for obj in dir(pd.Series().str) if obj[0] != "_"]
+    modin_dir = [obj for obj in dir(pd.Series.str) if obj[0] != "_"]
     pandas_dir = [obj for obj in dir(pandas.Series.str) if obj[0] != "_"]
 
     missing_from_modin = set(pandas_dir) - set(modin_dir)