Repository: ydataai/ydata-profiling

Issue:
Please consider update version requirement of package `visions`
**Missing functionality**
Currently, in `requirements.txt`, the version of visions is set to `0.6.0`
```
visions[type_image_path]==0.6.0
```


**Proposed feature**
Currently, visions version is 0.7.1. 
Please consider update the version, or set to `visions[type_image_path]>=0.6.0`



**Additional context**
Install pandas profiling break some package that required feature in visions 0.7



Discussion:

Resolving patch:
diff --git a/src/pandas_profiling/model/summary_algorithms.py b/src/pandas_profiling/model/summary_algorithms.py
--- a/src/pandas_profiling/model/summary_algorithms.py
+++ b/src/pandas_profiling/model/summary_algorithms.py
@@ -1,12 +1,11 @@
 import contextlib
 import functools
-from typing import Tuple
+from typing import Callable, Tuple, TypeVar
 from urllib.parse import urlsplit
 
 import numpy as np
 import pandas as pd
 from pandas.core.arrays.integer import _IntegerDtype
-from visions.utils import func_nullable_series_contains
 
 from pandas_profiling.config import config
 from pandas_profiling.model.summary_helpers import (
@@ -22,6 +21,8 @@
     word_summary,
 )
 
+T = TypeVar("T")
+
 
 def describe_counts(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
     """Counts the values in a series (with and without NaN, distinct).
@@ -65,9 +66,11 @@ def describe_counts(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
     return series, summary
 
 
-def series_hashable(fn):
+def series_hashable(
+    fn: Callable[[pd.Series, dict], Tuple[pd.Series, dict]]
+) -> Callable[[pd.Series, dict], Tuple[pd.Series, dict]]:
     @functools.wraps(fn)
-    def inner(series, summary):
+    def inner(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
         if not summary["hashable"]:
             return series, summary
         return fn(series, summary)
@@ -75,6 +78,21 @@ def inner(series, summary):
     return inner
 
 
+def series_handle_nulls(
+    fn: Callable[[pd.Series, dict], Tuple[pd.Series, dict]]
+) -> Callable[[pd.Series, dict], Tuple[pd.Series, dict]]:
+    """Decorator for nullable series"""
+
+    @functools.wraps(fn)
+    def inner(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
+        if series.hasnans:
+            series = series.dropna()
+
+        return fn(series, summary)
+
+    return inner
+
+
 @series_hashable
 def describe_supported(
     series: pd.Series, series_description: dict
@@ -163,7 +181,7 @@ def numeric_stats_numpy(present_values, series, series_description):
 
 
 @series_hashable
-@func_nullable_series_contains
+@series_handle_nulls
 def describe_numeric_1d(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
     """Describe a numeric series.
     Args:
@@ -255,7 +273,7 @@ def describe_numeric_1d(series: pd.Series, summary: dict) -> Tuple[pd.Series, di
 
 
 @series_hashable
-@func_nullable_series_contains
+@series_handle_nulls
 def describe_date_1d(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
     """Describe a date series.
 
@@ -287,7 +305,7 @@ def describe_date_1d(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]
 
 
 @series_hashable
-@func_nullable_series_contains
+@series_handle_nulls
 def describe_categorical_1d(series: pd.Series, summary: dict) -> Tuple[pd.Series, dict]:
     """Describe a categorical series.
 
diff --git a/src/pandas_profiling/model/typeset.py b/src/pandas_profiling/model/typeset.py
--- a/src/pandas_profiling/model/typeset.py
+++ b/src/pandas_profiling/model/typeset.py
@@ -5,10 +5,10 @@
 
 import pandas as pd
 import visions
+from multimethod import multimethod
 from pandas.api import types as pdt
+from visions.backends.pandas.series_utils import series_handle_nulls, series_not_empty
 from visions.relations import IdentityRelation, InferenceRelation
-from visions.utils import nullable_series_contains
-from visions.utils.series_utils import series_not_empty
 
 from pandas_profiling.config import config
 from pandas_profiling.model.typeset_relations import (
@@ -28,22 +28,22 @@ class Unsupported(visions.Generic):
 
 
 class Numeric(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
+    @staticmethod
+    def get_relations():
         return [
-            IdentityRelation(cls, Unsupported),
+            IdentityRelation(Unsupported),
             InferenceRelation(
-                cls,
                 Categorical,
                 relationship=category_is_numeric,
                 transformer=category_to_numeric,
             ),
         ]
 
-    @classmethod
-    @nullable_series_contains
+    @staticmethod
+    @multimethod
     @series_not_empty
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         return pdt.is_numeric_dtype(series) and not pdt.is_bool_dtype(series)
 
 
@@ -60,61 +60,64 @@ def to_date(series):
 
 
 class DateTime(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
+    @staticmethod
+    def get_relations():
         return [
-            IdentityRelation(cls, Unsupported),
+            IdentityRelation(Unsupported),
         ]
 
-    @classmethod
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @staticmethod
+    @multimethod
+    @series_not_empty
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         return pdt.is_datetime64_any_dtype(series)
 
 
 class Categorical(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
+    @staticmethod
+    def get_relations():
         return [
-            IdentityRelation(cls, Unsupported),
+            IdentityRelation(Unsupported),
             InferenceRelation(
-                cls,
                 Numeric,
                 relationship=numeric_is_category,
                 transformer=to_category,
             ),
         ]
 
-    @classmethod
+    @staticmethod
+    @multimethod
     @series_not_empty
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         is_valid_dtype = pdt.is_categorical_dtype(series) and not pdt.is_bool_dtype(
             series
         )
         if is_valid_dtype:
             return True
 
-        return series_is_string(series)
+        return series_is_string(series, state)
 
 
 class Boolean(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
+    @staticmethod
+    def get_relations():
         # Numeric [0, 1] goes via Categorical with distinct_count_without_nan <= 2
         return [
-            IdentityRelation(cls, Unsupported),
+            IdentityRelation(Unsupported),
             InferenceRelation(
-                cls,
                 Categorical,
                 relationship=string_is_bool,
                 transformer=lambda s, st: to_bool(string_to_bool(s, st)),
             ),
         ]
 
-    @classmethod
+    @staticmethod
+    @multimethod
     @series_not_empty
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         if pdt.is_object_dtype(series):
             try:
                 return series.isin({True, False}).all()
@@ -125,14 +128,14 @@ def contains_op(cls, series: pd.Series, state: dict) -> bool:
 
 
 class URL(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
-        relations = [IdentityRelation(cls, Categorical)]
-        return relations
-
-    @classmethod
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @staticmethod
+    def get_relations():
+        return [IdentityRelation(Categorical)]
+
+    @staticmethod
+    @multimethod
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         # TODO: use coercion utils
         try:
             url_gen = (urlparse(x) for x in series)
@@ -142,14 +145,14 @@ def contains_op(cls, series: pd.Series, state: dict) -> bool:
 
 
 class Path(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
-        relations = [IdentityRelation(cls, Categorical)]
-        return relations
-
-    @classmethod
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @staticmethod
+    def get_relations():
+        return [IdentityRelation(Categorical)]
+
+    @staticmethod
+    @multimethod
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         # TODO: use coercion utils
         try:
             return all(os.path.isabs(p) for p in series)
@@ -158,37 +161,38 @@ def contains_op(cls, series: pd.Series, state: dict) -> bool:
 
 
 class File(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
-        relations = [IdentityRelation(cls, Path)]
-        return relations
-
-    @classmethod
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @staticmethod
+    def get_relations():
+        return [IdentityRelation(Path)]
+
+    @staticmethod
+    @multimethod
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         return all(os.path.exists(p) for p in series)
 
 
 class Image(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
-        relations = [IdentityRelation(cls, File)]
-        return relations
-
-    @classmethod
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @staticmethod
+    def get_relations():
+        return [IdentityRelation(File)]
+
+    @staticmethod
+    @multimethod
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         return all(imghdr.what(p) for p in series)
 
 
 class Complex(visions.VisionsBaseType):
-    @classmethod
-    def get_relations(cls):
-        return [IdentityRelation(cls, Numeric)]
-
-    @classmethod
-    @nullable_series_contains
-    def contains_op(cls, series: pd.Series, state: dict) -> bool:
+    @staticmethod
+    def get_relations():
+        return [IdentityRelation(Numeric)]
+
+    @staticmethod
+    @multimethod
+    @series_handle_nulls
+    def contains_op(series: pd.Series, state: dict) -> bool:
         return pdt.is_complex_dtype(series)
 
 
diff --git a/src/pandas_profiling/model/typeset_relations.py b/src/pandas_profiling/model/typeset_relations.py
--- a/src/pandas_profiling/model/typeset_relations.py
+++ b/src/pandas_profiling/model/typeset_relations.py
@@ -3,7 +3,7 @@
 import numpy as np
 import pandas as pd
 from pandas.api import types as pdt
-from visions.utils import func_nullable_series_contains
+from visions.backends.pandas.series_utils import series_handle_nulls
 
 from pandas_profiling.config import config
 
@@ -35,7 +35,7 @@ def get_boolean_map():
 
 
 def string_is_bool(series, state) -> bool:
-    @func_nullable_series_contains
+    @series_handle_nulls
     @try_func
     def tester(s: pd.Series, state: dict) -> bool:
         return s.str.lower().isin(PP_bool_map.keys()).all()
@@ -67,12 +67,12 @@ def to_category(series, state):
     return val
 
 
-@func_nullable_series_contains
+@series_handle_nulls
 def series_is_string(series: pd.Series, state: dict) -> bool:
     return all(isinstance(v, str) for v in series)
 
 
-@func_nullable_series_contains
+@series_handle_nulls
 def category_is_numeric(series, state):
     if pdt.is_bool_dtype(series) or object_is_bool(series, state):
         return False
@@ -100,7 +100,7 @@ def to_bool(series: pd.Series) -> pd.Series:
     return series.astype(dtype)
 
 
-@func_nullable_series_contains
+@series_handle_nulls
 def object_is_bool(series: pd.Series, state) -> bool:
     if pdt.is_object_dtype(series):
         bool_set = {True, False}


Test patch:
diff --git a/tests/unit/test_typeset_custom.py b/tests/unit/test_typeset_custom.py
--- a/tests/unit/test_typeset_custom.py
+++ b/tests/unit/test_typeset_custom.py
@@ -131,7 +131,7 @@ def get_profiling_series():
         "nullable_int": pd.Series([1, None], dtype="Int64"),
     }
 
-    return [pd.Series(values, name=key) for key, values in data.items()]
+    return {key: pd.Series(values, name=key) for key, values in data.items()}
 
 
 series = get_profiling_series()
@@ -191,7 +191,7 @@ def __init__(self, name, contains_type, infer_type, cast_result=None):
 
 
 @pytest.mark.parametrize(**get_contains_cases(series, contains_map, typeset))
-def test_contains(series, type, member):
+def test_contains(name, series, type, member):
     """Test the generated combinations for "series in type"
 
     Args:
@@ -200,7 +200,7 @@ def test_contains(series, type, member):
         member: the result
     """
     config["vars"]["num"]["low_categorical_threshold"].set(0)
-    result, message = contains(series, type, member)
+    result, message = contains(name, series, type, member)
     assert result, message
 
 
@@ -240,7 +240,7 @@ def test_contains(series, type, member):
 
 
 @pytest.mark.parametrize(**get_inference_cases(series, inference_map, typeset))
-def test_inference(series, type, typeset, difference):
+def test_inference(name, series, type, typeset, difference):
     """Test the generated combinations for "inference(series) == type"
 
     Args:
@@ -248,7 +248,7 @@ def test_inference(series, type, typeset, difference):
         type: the type to test against
     """
     config["vars"]["num"]["low_categorical_threshold"].set(0)
-    result, message = infers(series, type, typeset, difference)
+    result, message = infers(name, series, type, typeset, difference)
     assert result, message
 
 
@@ -289,7 +289,7 @@ def test_inference(series, type, typeset, difference):
 
 
 @pytest.mark.parametrize(**get_convert_cases(series, convert_map, typeset))
-def test_conversion(source_type, relation_type, series, member):
+def test_conversion(name, source_type, relation_type, series, member):
     """Test the generated combinations for "convert(series) == type" and "infer(series) = source_type"
 
     Args:
@@ -297,5 +297,5 @@ def test_conversion(source_type, relation_type, series, member):
         source_type: the type to test against
     """
     config["vars"]["num"]["low_categorical_threshold"].set(2)
-    result, message = convert(source_type, relation_type, series, member)
+    result, message = convert(name, source_type, relation_type, series, member)
     assert result, message
diff --git a/tests/unit/test_typeset_default.py b/tests/unit/test_typeset_default.py
--- a/tests/unit/test_typeset_default.py
+++ b/tests/unit/test_typeset_default.py
@@ -1,14 +1,8 @@
-import datetime
 import os
-import pathlib
-import uuid
-from ipaddress import IPv4Address, IPv6Address
-from pathlib import PurePosixPath, PureWindowsPath
-from urllib.parse import urlparse
 
-import numpy as np
 import pandas as pd
 import pytest
+from visions.test.series import get_series
 from visions.test.utils import (
     contains,
     convert,
@@ -17,7 +11,6 @@
     get_inference_cases,
     infers,
 )
-from visions.types.email_address import FQDA
 
 from pandas_profiling.config import config
 from pandas_profiling.model.typeset import (
@@ -38,441 +31,6 @@
 
 base_path = os.path.abspath(os.path.dirname(__file__))
 
-
-# Workaround pending release https://github.com/dylan-profiler/visions/issues/162
-def get_series():
-    test_series = [
-        # Int Series
-        pd.Series([1, 2, 3], name="int_series"),
-        pd.Series(range(10), name="int_range"),
-        pd.Series([1, 2, 3], name="Int64_int_series", dtype="Int64"),
-        pd.Series([1, 2, 3, np.nan], name="Int64_int_nan_series", dtype="Int64"),
-        pd.Series([1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0], name="int_series_boolean"),
-        # Count
-        pd.Series(np.array([1, 2, 3, 4], dtype=np.uint32), name="np_uint32"),
-        pd.Series([1, 2, 3, 4], dtype="UInt32", name="pd_uint32"),
-        # Categorical
-        pd.Series([1, 2, 3], name="categorical_int_series", dtype="category"),
-        pd.Series(
-            pd.Categorical(
-                ["A", "B", "C", "C", "B", "A"],
-                categories=["A", "B", "C"],
-                ordered=False,
-            ),
-            name="categorical_char",
-        ),
-        pd.Series([1.0, 2.0, 3.1], dtype="category", name="categorical_float_series"),
-        pd.Series(
-            ["Georgia", "Sam"], dtype="category", name="categorical_string_series"
-        ),
-        pd.Series(
-            [complex(0, 0), complex(1, 2), complex(3, -1)],
-            name="categorical_complex_series",
-            dtype="category",
-        ),
-        # Ordinal
-        pd.Series(
-            pd.Categorical(
-                ["A", "B", "C", "C", "B", "A"], categories=["A", "B", "C"], ordered=True
-            ),
-            name="ordinal",
-        ),
-        # Float Series
-        pd.Series([1.0, 2.1, 3.0], name="float_series"),
-        pd.Series([1.0, 2.5, np.nan], name="float_nan_series"),
-        pd.Series([1.0, 2.0, 3.0, 4.0], name="float_series2"),
-        pd.Series(np.array([1.2, 2, 3, 4], dtype=np.float64), name="float_series3"),
-        pd.Series([1, 2, 3.05, 4], dtype=np.float64, name="float_series4"),
-        pd.Series([np.nan, 1.2], name="float_series5"),
-        pd.Series([np.nan, 1.1], dtype=np.single, name="float_series6"),
-        pd.Series([np.inf, np.NINF, np.PINF, 1000000.0, 5.5], name="float_with_inf"),
-        pd.Series([np.inf, np.NINF, np.Infinity, np.PINF], name="inf_series"),
-        pd.Series([1, 2, np.nan], name="int_nan_series"),
-        # Nan Series
-        pd.Series([np.nan], name="nan_series"),
-        pd.Series([np.nan, np.nan, np.nan, np.nan], name="nan_series_2"),
-        # String Series
-        pd.Series(["Patty", "Valentine"], name="string_series"),
-        pd.Series(["mack", "the", "finger"], name="string_unicode_series"),
-        pd.Series(
-            np.array(["upper", "hall"], dtype=np.unicode_),
-            name="string_np_unicode_series",
-        ),
-        pd.Series(["1.0", "2.0", np.nan], name="string_num_nan"),
-        pd.Series(["1,000.0", "2.1", np.nan], name="string_with_sep_num_nan"),
-        pd.Series(["1.0", "2.0", "3.0"], name="string_num"),
-        pd.Series(["1.0", "45.67", np.nan], name="string_flt_nan"),
-        pd.Series(["1.0", "45.67", "3.5"], name="string_flt"),
-        pd.Series(
-            [
-                "I was only robbing the register,",
-                "I hope you understand",
-                "One of us had better call up the cops",
-                "In the hot New Jersey night",
-                np.nan,
-            ],
-            name="string_str_nan",
-        ),
-        pd.Series(["True", "False", None], name="string_bool_nan"),
-        pd.Series(range(20), name="int_str_range").astype("str"),
-        pd.Series(
-            [
-                "http://www.cwi.nl:80/%7Eguido/Python.html",
-                "https://github.com/dylan-profiling/hurricane",
-            ],
-            name="str_url",
-        ),
-        pd.Series(
-            [r"C:\\home\\user\\file.txt", r"C:\\home\\user\\test2.txt"],
-            name="path_series_windows_str",
-        ),
-        pd.Series(
-            [r"/home/user/file.txt", r"/home/user/test2.txt"],
-            name="path_series_linux_str",
-        ),
-        pd.Series(["0011", "12"], name="str_int_leading_zeros"),
-        pd.Series(["0.0", "0.04", "0"], name="str_float_non_leading_zeros"),
-        pd.Series(["0.0", "0.000", "0", "2"], name="str_int_zeros"),
-        # Bool Series
-        pd.Series([True, False], name="bool_series"),
-        pd.Series([True, False, None], name="bool_nan_series"),
-        pd.Series([True, False, None], name="nullable_bool_series", dtype=btype),
-        pd.Series([True, False, False, True], name="bool_series2", dtype=bool),
-        pd.Series([True, False, False, True], name="bool_series2", dtype=bool),
-        pd.Series(np.array([1, 0, 0, 1], dtype=bool), name="bool_series3"),
-        # Complex Series
-        pd.Series(
-            [complex(0, 0), complex(1, 2), complex(3, -1)],
-            name="complex_series",
-        ),
-        pd.Series(
-            [
-                complex(0, 0),
-                complex(1, 2),
-                complex(3, -1),
-                complex(np.nan, np.nan),
-            ],
-            name="complex_series_nan",
-        ),
-        pd.Series(["(1+1j)", "(2+2j)", "(10+100j)"], name="str_complex"),
-        pd.Series(["(1+1j)", "(2+2j)", "(10+100j)", "NaN"], name="str_complex_nan"),
-        pd.Series(
-            [complex(0, 0), complex(1, 2), complex(3, -1), np.nan],
-            name="complex_series_nan_2",
-        ),
-        pd.Series(
-            [complex(0, 0), complex(1, 2), complex(3, -1), np.nan],
-            name="complex_series_py_nan",
-        ),
-        pd.Series(
-            [complex(0, 0), complex(1, 2), complex(3, -1)], name="complex_series_py"
-        ),
-        pd.Series(
-            [
-                complex(0, 0),
-                complex(1, 0),
-                complex(3, 0),
-                complex(-1, 0),
-            ],
-            name="complex_series_float",
-        ),
-        # Datetime Series
-        pd.Series(["1937-05-06", "20/4/2014"], name="string_date"),
-        pd.Series(["1941-05-24", "13/10/2016"], name="timestamp_string_series"),
-        pd.to_datetime(
-            pd.Series(
-                [datetime.datetime(2017, 3, 5, 12, 2), datetime.datetime(2019, 12, 4)],
-                name="timestamp_series",
-            )
-        ),
-        pd.to_datetime(
-            pd.Series(
-                [
-                    datetime.datetime(2017, 3, 5),
-                    datetime.datetime(2019, 12, 4, 3, 2, 0),
-                    pd.NaT,
-                ],
-                name="timestamp_series_nat",
-            )
-        ),
-        pd.to_datetime(
-            pd.Series(
-                [datetime.datetime(2017, 3, 5), datetime.datetime(2019, 12, 4), pd.NaT],
-                name="date_series_nat",
-            )
-        ),
-        pd.Series(
-            pd.date_range(
-                start="2013-05-18 12:00:01",
-                periods=2,
-                freq="H",
-                tz="Europe/Brussels",
-                name="timestamp_aware_series",
-            )
-        ),
-        pd.to_datetime(
-            pd.Series(
-                [
-                    datetime.date(2011, 1, 1),
-                    datetime.date(2012, 1, 2),
-                    datetime.date(2013, 1, 1),
-                ],
-                name="datetime",
-            )
-        ),
-        # Date series
-        pd.Series(
-            [
-                datetime.date(2011, 1, 1),
-                datetime.date(2012, 1, 2),
-                datetime.date(2013, 1, 1),
-            ],
-            name="date",
-        ),
-        # Time series
-        pd.Series(
-            [
-                datetime.time(8, 43, 12),
-                datetime.time(9, 43, 12),
-                datetime.time(10, 43, 12),
-            ],
-            name="time",
-        ),
-        # http://pandas-docs.github.io/pandas-docs-travis/user_guide/timeseries.html#timestamp-limitations
-        # Timedelta Series
-        pd.Series([pd.Timedelta(days=i) for i in range(3)], name="timedelta_series"),
-        pd.Series(
-            [pd.Timedelta(days=i) for i in range(3)] + [pd.NaT],
-            name="timedelta_series_nat",
-        ),
-        pd.Series(
-            [
-                pd.Timedelta("1 days 00:03:43"),
-                pd.Timedelta("5 days 12:33:57"),
-                pd.Timedelta("0 days 01:25:07"),
-                pd.Timedelta("-2 days 13:46:56"),
-                pd.Timedelta("1 days 23:49:25"),
-            ],
-            name="timedelta_negative",
-        ),
-        # Path Series
-        pd.Series(
-            [
-                PurePosixPath("/home/user/file.txt"),
-                PurePosixPath("/home/user/test2.txt"),
-            ],
-            name="path_series_linux",
-        ),
-        pd.Series(
-            [
-                PurePosixPath("/home/user/file.txt"),
-                PurePosixPath("/home/user/test2.txt"),
-                None,
-            ],
-            name="path_series_linux_missing",
-        ),
-        pd.Series(
-            [
-                PureWindowsPath("C:\\home\\user\\file.txt"),
-                PureWindowsPath("C:\\home\\user\\test2.txt"),
-            ],
-            name="path_series_windows",
-        ),
-        # Url Series
-        pd.Series(
-            [
-                urlparse("http://www.cwi.nl:80/%7Eguido/Python.html"),
-                urlparse("https://github.com/dylan-profiling/hurricane"),
-            ],
-            name="url_series",
-        ),
-        pd.Series(
-            [
-                urlparse("http://www.cwi.nl:80/%7Eguido/Python.html"),
-                urlparse("https://github.com/dylan-profiling/hurricane"),
-                np.nan,
-            ],
-            name="url_nan_series",
-        ),
-        pd.Series(
-            [
-                urlparse("http://www.cwi.nl:80/%7Eguido/Python.html"),
-                urlparse("https://github.com/dylan-profiling/hurricane"),
-                None,
-            ],
-            name="url_none_series",
-        ),
-        # UUID Series
-        pd.Series(
-            [
-                uuid.UUID("0b8a22ca-80ad-4df5-85ac-fa49c44b7ede"),
-                uuid.UUID("aaa381d6-8442-4f63-88c8-7c900e9a23c6"),
-                uuid.UUID("00000000-0000-0000-0000-000000000000"),
-            ],
-            name="uuid_series",
-        ),
-        pd.Series(
-            [
-                uuid.UUID("0b8a22ca-80ad-4df5-85ac-fa49c44b7ede"),
-                uuid.UUID("aaa381d6-8442-4f63-88c8-7c900e9a23c6"),
-                uuid.UUID("00000000-0000-0000-0000-000000000000"),
-                None,
-            ],
-            name="uuid_series_missing",
-        ),
-        pd.Series(
-            [
-                "0b8a22ca-80ad-4df5-85ac-fa49c44b7ede",
-                "aaa381d6-8442-4f63-88c8-7c900e9a23c6",
-                "00000000-0000-0000-0000-000000000000",
-            ],
-            name="uuid_series_str",
-        ),
-        # Object Series
-        pd.Series([[1, ""], [2, "Rubin"], [3, "Carter"]], name="mixed_list[str,int]"),
-        pd.Series(
-            [{"why": "did you"}, {"bring him": "in for he"}, {"aint": "the guy"}],
-            name="mixed_dict",
-        ),
-        pd.Series(
-            [pd.to_datetime, pd.to_timedelta, pd.read_json, pd.to_pickle],
-            name="callable",
-        ),
-        pd.Series([pd, np], name="module"),
-        pd.Series(["1.1", "2"], name="textual_float"),
-        pd.Series(["1.1", "2", "NAN"], name="textual_float_nan"),
-        # Object (Mixed, https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.api.types.infer_dtype.html)
-        pd.Series(["a", 1], name="mixed_integer"),
-        pd.Series([True, False, np.nan], name="mixed"),
-        pd.Series([[True], [False], [False]], name="mixed_list"),
-        pd.Series([[1, ""], [2, "Rubin"], [3, "Carter"]], name="mixed_list[str,int]"),
-        pd.Series(
-            [{"why": "did you"}, {"bring him": "in for he"}, {"aint": "the guy"}],
-            name="mixed_dict",
-        ),
-        # IP
-        pd.Series([IPv4Address("127.0.0.1"), IPv4Address("127.0.0.1")], name="ip"),
-        pd.Series(["127.0.0.1", "127.0.0.1"], name="ip_str"),
-        # Empty
-        pd.Series([], name="empty", dtype=np.float64),
-        pd.Series([], name="empty_float", dtype=float),
-        pd.Series([], name="empty_int64", dtype="Int64"),
-        pd.Series([], name="empty_object", dtype="object"),
-        pd.Series([], name="empty_bool", dtype=bool),
-        # IP
-        pd.Series([IPv4Address("127.0.0.1"), IPv4Address("127.0.0.1")], name="ip"),
-        pd.Series(
-            [IPv4Address("127.0.0.1"), None, IPv4Address("127.0.0.1")],
-            name="ip_missing",
-        ),
-        pd.Series(
-            [IPv6Address("0:0:0:0:0:0:0:1"), IPv4Address("127.0.0.1")],
-            name="ip_mixed_v4andv6",
-        ),
-        pd.Series(["127.0.0.1", "127.0.0.1"], name="ip_str"),
-        # File
-        pd.Series(
-            [
-                pathlib.Path(os.path.join(base_path, "series.py")).absolute(),
-                pathlib.Path(os.path.join(base_path, "__init__.py")).absolute(),
-                pathlib.Path(os.path.join(base_path, "utils.py")).absolute(),
-            ],
-            name="file_test_py",
-        ),
-        pd.Series(
-            [
-                pathlib.Path(os.path.join(base_path, "..", "py.typed")).absolute(),
-                pathlib.Path(
-                    os.path.join(
-                        base_path, "..", "visualisation", "circular_packing.html"
-                    )
-                ).absolute(),
-                pathlib.Path(os.path.join(base_path, "series.py")).absolute(),
-            ],
-            name="file_mixed_ext",
-        ),
-        pd.Series(
-            [
-                pathlib.Path(os.path.join(base_path, "series.py")).absolute(),
-                None,
-                pathlib.Path(os.path.join(base_path, "__init__.py")).absolute(),
-                None,
-                pathlib.Path(os.path.join(base_path, "utils.py")).absolute(),
-            ],
-            name="file_test_py_missing",
-        ),
-        # Image
-        pd.Series(
-            [
-                pathlib.Path(
-                    os.path.join(
-                        base_path,
-                        "../visualisation/typesets/typeset_complete.png",
-                    )
-                ).absolute(),
-                pathlib.Path(
-                    os.path.join(
-                        base_path,
-                        r"../visualisation/typesets/typeset_standard.png",
-                    )
-                ).absolute(),
-                pathlib.Path(
-                    os.path.join(
-                        base_path,
-                        r"../visualisation/typesets/typeset_geometry.png",
-                    )
-                ).absolute(),
-            ],
-            name="image_png",
-        ),
-        pd.Series(
-            [
-                pathlib.Path(
-                    os.path.join(
-                        base_path,
-                        r"../visualisation/typesets/typeset_complete.png",
-                    )
-                ).absolute(),
-                pathlib.Path(
-                    os.path.join(
-                        base_path,
-                        r"../visualisation/typesets/typeset_standard.png",
-                    )
-                ).absolute(),
-                None,
-                pathlib.Path(
-                    os.path.join(
-                        base_path,
-                        r"../visualisation/typesets/typeset_geometry.png",
-                    )
-                ).absolute(),
-                None,
-            ],
-            name="image_png_missing",
-        ),
-        # Email
-        pd.Series(
-            [FQDA("test", "example.com"), FQDA("info", "example.eu")],
-            name="email_address",
-        ),
-        pd.Series(
-            [FQDA("test", "example.com"), FQDA("info", "example.eu"), None],
-            name="email_address_missing",
-        ),
-        pd.Series(["test@example.com", "info@example.eu"], name="email_address_str"),
-    ]
-
-    if int(pd.__version__.split(".")[0]) >= 1:
-        pandas_1_series = [
-            pd.Series(
-                ["Patty", "Valentine"], dtype="string", name="string_dtype_series"
-            )
-        ]
-        test_series.extend(pandas_1_series)
-
-    return test_series
-
-
 series = get_series()
 
 typeset = ProfilingTypeSet()
@@ -502,6 +60,7 @@ def get_series():
         "complex_series_py_nan",
         "complex_series_nan_2",
         "complex_series_float",
+        "complex_series_py_float",
     },
     Categorical: {
         "categorical_float_series",
@@ -536,6 +95,8 @@ def get_series():
         "str_float_non_leading_zeros",
         "str_int_zeros",
         "str_complex_nan",
+        "all_null_empty_str",
+        "py_datetime_str",
     },
     Boolean: {
         "bool_series",
@@ -596,11 +157,14 @@ def get_series():
     "ip_mixed_v4andv6",
     "email_address_missing",
     "email_address",
+    "all_null_none",
+    "all_null_nan",
+    "all_null_nat",
 }
 
 
 @pytest.mark.parametrize(**get_contains_cases(series, contains_map, typeset))
-def test_contains(series, type, member):
+def test_contains(name, series, type, member):
     """Test the generated combinations for "series in type"
 
     Args:
@@ -609,7 +173,7 @@ def test_contains(series, type, member):
         member: the result
     """
     config["vars"]["num"]["low_categorical_threshold"].set(0)
-    result, message = contains(series, type, member)
+    result, message = contains(name, series, type, member)
     assert result, message
 
 
@@ -718,13 +282,19 @@ def test_contains(series, type, member):
     "str_complex_nan": Categorical,
     "email_address": Unsupported,
     "email_address_missing": Unsupported,
+    "all_null_nat": Unsupported,
+    "all_null_empty_str": Categorical,
+    "py_datetime_str": Categorical,
+    "all_null_none": Unsupported,
+    "complex_series_py_float": Numeric,
+    "all_null_nan": Unsupported,
 }
 if int(pd.__version__[0]) >= 1:
     inference_map["string_dtype_series"] = Categorical
 
 
 @pytest.mark.parametrize(**get_inference_cases(series, inference_map, typeset))
-def test_inference(series, type, typeset, difference):
+def test_inference(name, series, type, typeset, difference):
     """Test the generated combinations for "inference(series) == type"
 
     Args:
@@ -732,7 +302,7 @@ def test_inference(series, type, typeset, difference):
         type: the type to test against
     """
     config["vars"]["num"]["low_categorical_threshold"].set(0)
-    result, message = infers(series, type, typeset, difference)
+    result, message = infers(name, series, type, typeset, difference)
     assert result, message
 
 
@@ -773,7 +343,7 @@ def test_inference(series, type, typeset, difference):
 
 
 @pytest.mark.parametrize(**get_convert_cases(series, convert_map, typeset))
-def test_conversion(source_type, relation_type, series, member):
+def test_conversion(name, source_type, relation_type, series, member):
     """Test the generated combinations for "convert(series) == type" and "infer(series) = source_type"
 
     Args:
@@ -781,5 +351,5 @@ def test_conversion(source_type, relation_type, series, member):
         type: the type to test against
     """
     config["vars"]["num"]["low_categorical_threshold"].set(0)
-    result, message = convert(source_type, relation_type, series, member)
+    result, message = convert(name, source_type, relation_type, series, member)
     assert result, message