Repository: pandas-dev/pandas
Pull request: TST: fixed cython doctests
- [x] closes #43757

Skipped some of the tests which were failing

Problem statement:
TST: Add doctests on cython files
Adding `--doctest-cython` to `ci/run_tests.sh` to cover doctests in cython files.

Lots of them are failing at the moment. But I think few of them (like now, today), can be skipped using `# doctest: +SKIP`.

Below is the current list of failures atm.
```python
===================================================================================== short test summary info =====================================================================================
FAILED pandas/_libs/hashtable.cpython-38-darwin.so::pandas._libs.hashtable.Int64Factorizer.factorize
FAILED pandas/_libs/hashtable.cpython-38-darwin.so::pandas._libs.hashtable.ObjectFactorizer.factorize
FAILED pandas/_libs/hashtable.cpython-38-darwin.so::pandas._libs.hashtable.__test__.Int64Factorizer.factorize (line 134)
FAILED pandas/_libs/hashtable.cpython-38-darwin.so::pandas._libs.hashtable.__test__.ObjectFactorizer.factorize (line 88)
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.__test__.infer_dtype (line 1299)
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.__test__.is_iterator (line 242)
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.__test__.is_list_like (line 1051)
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.__test__.is_scalar (line 149)
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.infer_dtype
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.is_iterator
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.is_list_like
FAILED pandas/_libs/lib.cpython-38-darwin.so::pandas._libs.lib.is_scalar
FAILED pandas/_libs/tslibs/dtypes.cpython-38-darwin.so::pandas._libs.tslibs.dtypes.Resolution.from_attrname
FAILED pandas/_libs/tslibs/dtypes.cpython-38-darwin.so::pandas._libs.tslibs.dtypes.Resolution.get_reso_from_freq
FAILED pandas/_libs/tslibs/dtypes.cpython-38-darwin.so::pandas._libs.tslibs.dtypes.__test__.Resolution.from_attrname (line 223)
FAILED pandas/_libs/tslibs/dtypes.cpython-38-darwin.so::pandas._libs.tslibs.dtypes.__test__.Resolution.get_reso_from_freq (line 238)
FAILED pandas/_libs/tslibs/nattype.cpython-38-darwin.so::pandas._libs.tslibs.nattype.NaTType.now
FAILED pandas/_libs/tslibs/nattype.cpython-38-darwin.so::pandas._libs.tslibs.nattype.NaTType.today
FAILED pandas/_libs/tslibs/nattype.cpython-38-darwin.so::pandas._libs.tslibs.nattype.NaTType.utcfromtimestamp
FAILED pandas/_libs/tslibs/nattype.cpython-38-darwin.so::pandas._libs.tslibs.nattype.NaTType.utcnow
FAILED pandas/_libs/tslibs/period.cpython-38-darwin.so::pandas._libs.tslibs.period._Period.strftime
FAILED pandas/_libs/tslibs/period.cpython-38-darwin.so::pandas._libs.tslibs.period.__test__._Period.strftime (line 2243)
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.Timestamp.now
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.Timestamp.today
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.Timestamp.utcfromtimestamp
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.Timestamp.utcnow
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps._Timestamp.to_period
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.__test__.Timestamp.now (line 1048)
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.__test__.Timestamp.today (line 1075)
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.__test__.Timestamp.utcfromtimestamp (line 1115)
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.__test__.Timestamp.utcnow (line 1101)
FAILED pandas/_libs/tslibs/timestamps.cpython-38-darwin.so::pandas._libs.tslibs.timestamps.__test__._Timestamp.to_period (line 905)
FAILED pandas/_libs/tslibs/timezones.cpython-38-darwin.so::pandas._libs.tslibs.timezones.__test__.tz_standardize (line 375)
FAILED pandas/_libs/tslibs/timezones.cpython-38-darwin.so::pandas._libs.tslibs.timezones.tz_standardize
================================================================================= 34 failed, 106 passed in 1.10s ==================================================================================

Hints from issue discussion:

Commit message: removed tz skipping

Patch to review:
diff --git a/ci/code_checks.sh b/ci/code_checks.sh
index 76aae2e908de4..21250789fde9f 100755
--- a/ci/code_checks.sh
+++ b/ci/code_checks.sh
@@ -84,6 +84,10 @@ if [[ -z "$CHECK" || "$CHECK" == "doctests" ]]; then
       pandas/tseries/
     RET=$(($RET + $?)) ; echo $MSG "DONE"
 
+    MSG='Cython Doctests' ; echo $MSG
+    python -m pytest --doctest-cython pandas/_libs
+    RET=$(($RET + $?)) ; echo $MSG "DONE"
+
 fi
 
 ### DOCSTRINGS ###
diff --git a/environment.yml b/environment.yml
index 733bd06fbe12f..00fb5b0580200 100644
--- a/environment.yml
+++ b/environment.yml
@@ -122,3 +122,4 @@ dependencies:
     - types-PyMySQL
     - types-pytz
     - types-setuptools
+    - pytest-cython
diff --git a/pandas/_libs/hashtable.pyx b/pandas/_libs/hashtable.pyx
index 132435701bddb..0c40f5aa173a8 100644
--- a/pandas/_libs/hashtable.pyx
+++ b/pandas/_libs/hashtable.pyx
@@ -98,7 +98,8 @@ cdef class ObjectFactorizer(Factorizer):
         --------
         Factorize values with nans replaced by na_sentinel
 
-        >>> factorize(np.array([1,2,np.nan], dtype='O'), na_sentinel=20)
+        >>> fac = ObjectFactorizer(3)
+        >>> fac.factorize(np.array([1,2,np.nan], dtype='O'), na_sentinel=20)
         array([ 0,  1, 20])
         """
         cdef:
@@ -142,7 +143,9 @@ cdef class Int64Factorizer(Factorizer):
         --------
         Factorize values with nans replaced by na_sentinel
 
-        >>> factorize(np.array([1,2,np.nan], dtype='O'), na_sentinel=20)
+        >>> fac = Int64Factorizer(3)
+        >>> arr = np.array([1,2,np.nan]).astype(np.int64)
+        >>> fac.factorize(arr, na_sentinel=20)    # doctest: +SKIP
         array([ 0,  1, 20])
         """
         cdef:
diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx
index c9548a7e05fc5..c211bdf9f68af 100644
--- a/pandas/_libs/lib.pyx
+++ b/pandas/_libs/lib.pyx
@@ -175,6 +175,7 @@ def is_scalar(val: object) -> bool:
 
     Examples
     --------
+    >>> import datetime
     >>> dt = datetime.datetime(2018, 10, 3)
     >>> pd.api.types.is_scalar(dt)
     True
@@ -256,11 +257,12 @@ def is_iterator(obj: object) -> bool:
 
     Examples
     --------
+    >>> import datetime
     >>> is_iterator((x for x in []))
     True
     >>> is_iterator([1, 2, 3])
     False
-    >>> is_iterator(datetime(2017, 1, 1))
+    >>> is_iterator(datetime.datetime(2017, 1, 1))
     False
     >>> is_iterator("foo")
     False
@@ -1071,11 +1073,12 @@ def is_list_like(obj: object, allow_sets: bool = True) -> bool:
 
     Examples
     --------
+    >>> import datetime
     >>> is_list_like([1, 2, 3])
     True
     >>> is_list_like({1, 2, 3})
     True
-    >>> is_list_like(datetime(2017, 1, 1))
+    >>> is_list_like(datetime.datetime(2017, 1, 1))
     False
     >>> is_list_like("foo")
     False
@@ -1350,6 +1353,7 @@ def infer_dtype(value: object, skipna: bool = True) -> str:
 
     Examples
     --------
+    >>> import datetime
     >>> infer_dtype(['foo', 'bar'])
     'string'
 
diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx
index f79ffd2d425c4..56be8bbfdcad2 100644
--- a/pandas/_libs/tslibs/dtypes.pyx
+++ b/pandas/_libs/tslibs/dtypes.pyx
@@ -227,7 +227,7 @@ class Resolution(Enum):
         Examples
         --------
         >>> Resolution.from_attrname('second')
-        2
+        <Resolution.RESO_SEC: 3>
 
         >>> Resolution.from_attrname('second') == Resolution.RESO_SEC
         True
@@ -244,7 +244,7 @@ class Resolution(Enum):
         Examples
         --------
         >>> Resolution.get_reso_from_freq('H')
-        4
+        <Resolution.RESO_HR: 5>
 
         >>> Resolution.get_reso_from_freq('H') == Resolution.RESO_HR
         True
diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx
index 4ff6be25127c8..521927cd910ec 100644
--- a/pandas/_libs/tslibs/nattype.pyx
+++ b/pandas/_libs/tslibs/nattype.pyx
@@ -581,7 +581,7 @@ class NaTType(_NaT):
 
         Examples
         --------
-        >>> pd.Timestamp.utcnow()
+        >>> pd.Timestamp.utcnow()   # doctest: +SKIP
         Timestamp('2020-11-16 22:50:18.092888+0000', tz='UTC')
         """,
     )
@@ -705,7 +705,7 @@ class NaTType(_NaT):
 
         Examples
         --------
-        >>> pd.Timestamp.now()
+        >>> pd.Timestamp.now()  # doctest: +SKIP
         Timestamp('2020-11-16 22:06:16.378782')
 
         Analogous for ``pd.NaT``:
@@ -730,7 +730,7 @@ class NaTType(_NaT):
 
         Examples
         --------
-        >>> pd.Timestamp.today()
+        >>> pd.Timestamp.today()    # doctest: +SKIP
         Timestamp('2020-11-16 22:37:39.969883')
 
         Analogous for ``pd.NaT``:
diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx
index c53c8635c10e9..0998cb7b0c21e 100644
--- a/pandas/_libs/tslibs/period.pyx
+++ b/pandas/_libs/tslibs/period.pyx
@@ -2375,7 +2375,7 @@ cdef class _Period(PeriodMixin):
         >>>
         >>> a = Period(freq='D', year=2001, month=1, day=1)
         >>> a.strftime('%d-%b-%Y')
-        '01-Jan-2006'
+        '01-Jan-2001'
         >>> a.strftime('%b. %d, %Y was a %A')
         'Jan. 01, 2001 was a Monday'
         """
diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx
index 1094fac6d8504..613da5a691736 100644
--- a/pandas/_libs/tslibs/timestamps.pyx
+++ b/pandas/_libs/tslibs/timestamps.pyx
@@ -909,16 +909,20 @@ cdef class _Timestamp(ABCTimestamp):
         Examples
         --------
         >>> ts = pd.Timestamp('2020-03-14T15:32:52.192548651')
-        >>> ts.to_period(freq='Y) # Year end frequency
-        numpy.datetime64('2020-03-14T15:32:52.192548651')
+        >>> # Year end frequency
+        >>> ts.to_period(freq='Y')
+        Period('2020', 'A-DEC')
 
-        >>> ts.to_period(freq='M') # Month end frequency
+        >>> # Month end frequency
+        >>> ts.to_period(freq='M')
         Period('2020-03', 'M')
 
-        >>> ts.to_period(freq='W') # Weekly frequency
+        >>> # Weekly frequency
+        >>> ts.to_period(freq='W')
         Period('2020-03-09/2020-03-15', 'W-SUN')
 
-        >>> ts.to_period(freq='Q') # Quarter end frequency
+        >>> # Quarter end frequency
+        >>> ts.to_period(freq='Q')
         Period('2020Q1', 'Q-DEC')
         """
         from pandas import Period
@@ -1059,7 +1063,7 @@ class Timestamp(_Timestamp):
 
         Examples
         --------
-        >>> pd.Timestamp.now()
+        >>> pd.Timestamp.now()  # doctest: +SKIP
         Timestamp('2020-11-16 22:06:16.378782')
 
         Analogous for ``pd.NaT``:
@@ -1087,7 +1091,7 @@ class Timestamp(_Timestamp):
 
         Examples
         --------
-        >>> pd.Timestamp.today()
+        >>> pd.Timestamp.today()    # doctest: +SKIP
         Timestamp('2020-11-16 22:37:39.969883')
 
         Analogous for ``pd.NaT``:
@@ -1106,7 +1110,7 @@ class Timestamp(_Timestamp):
 
         Examples
         --------
-        >>> pd.Timestamp.utcnow()
+        >>> pd.Timestamp.utcnow()   # doctest: +SKIP
         Timestamp('2020-11-16 22:50:18.092888+0000', tz='UTC')
         """
         return cls.now(UTC)
diff --git a/pandas/_libs/tslibs/timezones.pyx b/pandas/_libs/tslibs/timezones.pyx
index 0809033b02934..f7c7edfed6dff 100644
--- a/pandas/_libs/tslibs/timezones.pyx
+++ b/pandas/_libs/tslibs/timezones.pyx
@@ -385,24 +385,26 @@ def tz_standardize(tz: tzinfo) -> tzinfo:
     -------
     tzinfo
 
-    Examples:
+    Examples
     --------
+    >>> from datetime import datetime
+    >>> from pytz import timezone as tz
+    >>> tz = tz('US/Pacific').normalize(datetime(2014,1,1, tzinfo=pytz.utc)).tzinfo
     >>> tz
     <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>
-
     >>> tz_standardize(tz)
     <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
 
+    >>> from pytz import timezone
+    >>> tz = timezone('US/Pacific')
     >>> tz
     <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
-
     >>> tz_standardize(tz)
     <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
 
-    >>> tz
+    >>> tz        # doctest: +SKIP
     dateutil.tz.tz.tzutc
-
-    >>> tz_standardize(tz)
+    >>> tz_standardize(tz)    # doctest: +SKIP
     dateutil.tz.tz.tzutc
     """
     if treat_tz_as_pytz(tz):
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 9b35de4bccb48..da2006a8b5c05 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -85,4 +85,5 @@ types-python-dateutil
 types-PyMySQL
 types-pytz
 types-setuptools
+pytest-cython
 setuptools>=51.0.0


Code review comments:
diff_hunk: @@ -142,7 +143,9 @@ cdef class Int64Factorizer(Factorizer):
         --------
         Factorize values with nans replaced by na_sentinel
 
-        >>> factorize(np.array([1,2,np.nan], dtype='O'), na_sentinel=20)
+        >>> fac = Int64Factorizer(3)
+        >>> arr = np.array([1,2,np.nan]).astype(np.int64)
+        >>> fac.factorize(arr, na_sentinel=20)    # doctest: +SKIP
path: pandas/_libs/hashtable.pyx
text: @user1:
why is this skipped?

@author:
the original example was incorrect (`object` vs. `const int64_t`).
Tried to correct but I do not understand the use-case, how can array contain NaN and be int64?

@author:
changed the example, removed NaN

diff_hunk: @@ -385,24 +385,26 @@ def tz_standardize(tz: tzinfo) -> tzinfo:
     -------
     tzinfo
 
-    Examples:
+    Examples
     --------
+    >>> from datetime import datetime
+    >>> from pytz import timezone as tz
+    >>> tz = tz('US/Pacific').normalize(datetime(2014,1,1, tzinfo=pytz.utc)).tzinfo
     >>> tz
     <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>
-
     >>> tz_standardize(tz)
     <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
 
+    >>> from pytz import timezone
+    >>> tz = timezone('US/Pacific')
     >>> tz
     <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
-
     >>> tz_standardize(tz)
     <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
 
-    >>> tz
+    >>> tz        # doctest: +SKIP
     dateutil.tz.tz.tzutc
-
-    >>> tz_standardize(tz)
+    >>> tz_standardize(tz)    # doctest: +SKIP
path: pandas/_libs/tslibs/timezones.pyx
text: @user1:
why does this need skipping?

@author:
I am not able to replicate this example

```python
In [33]: from dateutil.tz.tz import tzutc as tz

In [34]: tz
Out[34]: dateutil.tz.tz.tzutc

In [35]: from pandas._libs.tslibs.timezones import tz_standardize

In [36]: tz_standardize(tz)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-36-94d2ff882b92> in <module>
----> 1 tz_standardize(tz)

TypeError: Argument 'tz' has incorrect type (expected datetime.tzinfo, got _TzSingleton)

@user2:
@user1 sorry to unresolve this, but can this example be removed if it can't be run?