Repository: dask/dask

Issue:
`bcolz` no longer maintained
I recently noticed that [`bcolz` is no longer actively maintained](https://github.com/Blosc/bcolz/blob/500351db35c7d2a182e1d33e38daccf6a5ac7266/README.rst#unmaintained-package-notice). Dask DataFrame supports I/O operations with `bcolz` through our `from_bcolz` method. Should we consider deprecating `from_bcolz`? We don't get many `bcolz`-related issues reported, so things may continue to just work. At the same time, it will become more difficult to run our `bcolz`-related tests as time goes on. For example, today we don't test our `bcolz` support on Python 3.9:

https://github.com/dask/dask/blob/89d93a8264ce81c30207d83de96e3346a15365a4/continuous_integration/environment-3.9.yaml#L33-L34

xref https://github.com/dask/dask/pull/8275#discussion_r741359567




Discussion:
Given `bcolz` is no longer maintained and can't be installed from conda-forge on Python 3.9+, I think we should deprecate `from_bcolz`. @pavithraes would you mind looking into this when you get a moment. 

To do this I would emit a `FutureWarning` in Dask's `from_bcolz` with `warnings.warn` and then use `pytest.warns` in our existing `bcolz`-related tests to make sure the warning is emitted as expected  

Resolving patch:
diff --git a/dask/dataframe/io/io.py b/dask/dataframe/io/io.py
--- a/dask/dataframe/io/io.py
+++ b/dask/dataframe/io/io.py
@@ -12,7 +12,7 @@
 from ...dataframe.core import new_dd_object
 from ...delayed import delayed
 from ...highlevelgraph import HighLevelGraph
-from ...utils import M, ensure_dict
+from ...utils import M, _deprecated, ensure_dict
 from ..core import DataFrame, Index, Series, has_parallel_type, new_dd_object
 from ..shuffle import set_partition
 from ..utils import check_meta, insert_meta_param_description, is_series_like, make_meta
@@ -232,6 +232,7 @@ def from_pandas(data, npartitions=None, chunksize=None, sort=True, name=None):
     return new_dd_object(dsk, name, data, divisions)
 
 
+@_deprecated(after_version="2022.02.1")
 def from_bcolz(x, chunksize=None, categorize=True, index=None, lock=lock, **kwargs):
     """Read BColz CTable into a Dask Dataframe
 


Test patch:
diff --git a/dask/dataframe/io/tests/test_io.py b/dask/dataframe/io/tests/test_io.py
--- a/dask/dataframe/io/tests/test_io.py
+++ b/dask/dataframe/io/tests/test_io.py
@@ -1,3 +1,4 @@
+import contextlib
 from concurrent.futures import ThreadPoolExecutor
 from threading import Lock
 
@@ -112,6 +113,12 @@ def test_from_array_with_record_dtype():
     assert (d.compute().to_records(index=False) == x).all()
 
 
+@contextlib.contextmanager
+def check_bcolz_deprecation_warning():
+    with pytest.warns(FutureWarning, match="bcolz was deprecated"):
+        yield
+
+
 def test_from_bcolz_multiple_threads():
     bcolz = pytest.importorskip("bcolz")
 
@@ -119,13 +126,16 @@ def check(i):
         t = bcolz.ctable(
             [[1, 2, 3], [1.0, 2.0, 3.0], ["a", "b", "a"]], names=["x", "y", "a"]
         )
+
         d = dd.from_bcolz(t, chunksize=2)
+
         assert d.npartitions == 2
         assert is_categorical_dtype(d.dtypes["a"])
         assert list(d.x.compute(scheduler="sync")) == [1, 2, 3]
         assert list(d.a.compute(scheduler="sync")) == ["a", "b", "a"]
 
         d = dd.from_bcolz(t, chunksize=2, index="x")
+
         L = list(d.index.compute(scheduler="sync"))
         assert L == [1, 2, 3] or L == [1, 3, 2]
 
@@ -137,8 +147,9 @@ def check(i):
             dd.from_bcolz(t, chunksize=3).dask
         )
 
-    with ThreadPoolExecutor(5) as pool:
-        list(pool.map(check, range(5)))
+    with check_bcolz_deprecation_warning():
+        with ThreadPoolExecutor(5) as pool:
+            list(pool.map(check, range(5)))
 
 
 def test_from_bcolz():
@@ -147,32 +158,34 @@ def test_from_bcolz():
     t = bcolz.ctable(
         [[1, 2, 3], [1.0, 2.0, 3.0], ["a", "b", "a"]], names=["x", "y", "a"]
     )
-    d = dd.from_bcolz(t, chunksize=2)
-    assert d.npartitions == 2
-    assert is_categorical_dtype(d.dtypes["a"])
-    assert list(d.x.compute(scheduler="sync")) == [1, 2, 3]
-    assert list(d.a.compute(scheduler="sync")) == ["a", "b", "a"]
-    L = list(d.index.compute(scheduler="sync"))
-    assert L == [0, 1, 2]
-
-    d = dd.from_bcolz(t, chunksize=2, index="x")
-    L = list(d.index.compute(scheduler="sync"))
-    assert L == [1, 2, 3] or L == [1, 3, 2]
-
-    # Names
-    assert sorted(dd.from_bcolz(t, chunksize=2).dask) == sorted(
-        dd.from_bcolz(t, chunksize=2).dask
-    )
-    assert sorted(dd.from_bcolz(t, chunksize=2).dask) != sorted(
-        dd.from_bcolz(t, chunksize=3).dask
-    )
 
-    dsk = dd.from_bcolz(t, chunksize=3).dask
+    with check_bcolz_deprecation_warning():
+        d = dd.from_bcolz(t, chunksize=2)
+        assert d.npartitions == 2
+        assert is_categorical_dtype(d.dtypes["a"])
+        assert list(d.x.compute(scheduler="sync")) == [1, 2, 3]
+        assert list(d.a.compute(scheduler="sync")) == ["a", "b", "a"]
+        L = list(d.index.compute(scheduler="sync"))
+        assert L == [0, 1, 2]
+
+        d = dd.from_bcolz(t, chunksize=2, index="x")
+        L = list(d.index.compute(scheduler="sync"))
+        assert L == [1, 2, 3] or L == [1, 3, 2]
+
+        # Names
+        assert sorted(dd.from_bcolz(t, chunksize=2).dask) == sorted(
+            dd.from_bcolz(t, chunksize=2).dask
+        )
+        assert sorted(dd.from_bcolz(t, chunksize=2).dask) != sorted(
+            dd.from_bcolz(t, chunksize=3).dask
+        )
+
+        dsk = dd.from_bcolz(t, chunksize=3).dask
 
-    t.append((4, 4.0, "b"))
-    t.flush()
+        t.append((4, 4.0, "b"))
+        t.flush()
 
-    assert sorted(dd.from_bcolz(t, chunksize=2).dask) != sorted(dsk)
+        assert sorted(dd.from_bcolz(t, chunksize=2).dask) != sorted(dsk)
 
 
 def test_from_bcolz_no_lock():
@@ -182,9 +195,12 @@ def test_from_bcolz_no_lock():
     t = bcolz.ctable(
         [[1, 2, 3], [1.0, 2.0, 3.0], ["a", "b", "a"]], names=["x", "y", "a"], chunklen=2
     )
-    a = dd.from_bcolz(t, chunksize=2)
-    b = dd.from_bcolz(t, chunksize=2, lock=True)
-    c = dd.from_bcolz(t, chunksize=2, lock=False)
+
+    with check_bcolz_deprecation_warning():
+        a = dd.from_bcolz(t, chunksize=2)
+        b = dd.from_bcolz(t, chunksize=2, lock=True)
+        c = dd.from_bcolz(t, chunksize=2, lock=False)
+
     assert_eq(a, b)
     assert_eq(a, c)
 
@@ -202,7 +218,9 @@ def test_from_bcolz_filename():
         )
         t.flush()
 
-        d = dd.from_bcolz(fn, chunksize=2)
+        with check_bcolz_deprecation_warning():
+            d = dd.from_bcolz(fn, chunksize=2)
+
         assert list(d.x.compute()) == [1, 2, 3]
 
 
@@ -212,7 +230,10 @@ def test_from_bcolz_column_order():
     t = bcolz.ctable(
         [[1, 2, 3], [1.0, 2.0, 3.0], ["a", "b", "a"]], names=["x", "y", "a"]
     )
-    df = dd.from_bcolz(t, chunksize=2)
+
+    with check_bcolz_deprecation_warning():
+        df = dd.from_bcolz(t, chunksize=2)
+
     assert list(df.loc[0].compute().columns) == ["x", "y", "a"]