# Buggy code
def read_sql(cls, sql, con, index_col=None, **kwargs):
    """Reads a SQL query or database table into a DataFrame.
    Args:
        sql: string or SQLAlchemy Selectable (select or text object) SQL query to be
            executed or a table name.
        con: SQLAlchemy connectable (engine/connection) or database string URI or
            DBAPI2 connection (fallback mode)
        index_col: Column(s) to set as index(MultiIndex).
        kwargs: Pass into pandas.read_sql function.
    """
    if cls.read_sql_remote_task is None:
        return super(RayIO, cls).read_sql(sql, con, index_col=index_col, **kwargs)

    row_cnt_query = "SELECT COUNT(*) FROM ({} as foo)".format(sql)
    row_cnt = pandas.read_sql(row_cnt_query, con).squeeze()
    cols_names_df = pandas.read_sql(
        "SELECT * FROM ({} as foo) LIMIT 0".format(sql), con, index_col=index_col
    )
    cols_names = cols_names_df.columns
    num_parts = cls.frame_mgr_cls._compute_num_partitions()
    partition_ids = []
    index_ids = []
    limit = math.ceil(row_cnt / num_parts)
    for part in range(num_parts):
        offset = part * limit
        query = "SELECT * FROM ({} as foo) LIMIT {} OFFSET {}".format(
            sql, limit, offset
        )
        partition_id = cls.read_sql_remote_task._remote(
            args=(num_parts, query, con, index_col, kwargs),
            num_return_vals=num_parts + 1,
        )
        partition_ids.append(
            [cls.frame_partition_cls(obj) for obj in partition_id[:-1]]
        )
        index_ids.append(partition_id[-1])

    if index_col is None:  # sum all lens returned from partitions
        index_lens = ray.get(index_ids)
        new_index = pandas.RangeIndex(sum(index_lens))
    else:  # concat index returned from partitions
        index_lst = [x for part_index in ray.get(index_ids) for x in part_index]
        new_index = pandas.Index(index_lst).set_names(index_col)

    new_query_compiler = cls.query_compiler_cls(
        cls.frame_mgr_cls(np.array(partition_ids)), new_index, cols_names
    )
    return new_query_compiler


# Traceback (TypeError)
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/ray/worker.py", line 383, in put_object
self.store_and_register(object_id, value)
File "/usr/local/lib/python3.7/site-packages/ray/worker.py", line 317, in store_and_register
self.task_driver_id))
File "/usr/local/lib/python3.7/site-packages/ray/utils.py", line 475, in _wrapper
return orig_attr(*args, **kwargs)
File "pyarrow/_plasma.pyx", line 496, in pyarrow._plasma.PlasmaClient.put
File "pyarrow/serialization.pxi", line 355, in pyarrow.lib.serialize
File "pyarrow/serialization.pxi", line 150, in pyarrow.lib.SerializationContext._serialize_callback
File "/usr/local/lib/python3.7/site-packages/ray/cloudpickle/cloudpickle.py", line 952, in dumps
cp.dump(obj)
File "/usr/local/lib/python3.7/site-packages/ray/cloudpickle/cloudpickle.py", line 267, in dump
return Pickler.dump(self, obj)
File "/usr/local/lib/python3.7/pickle.py", line 437, in dump
self.save(obj)
File "/usr/local/lib/python3.7/pickle.py", line 524, in save
rv = reduce(self.proto)
TypeError: can't pickle _thread.RLock objects

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "src/main.py", line 55, in <module>
start_date=start_date)
File "/src/preparation/data_source.py", line 16, in import_data
data = self._load_data(app_ids, granularity, f'{date.today()}_{self._source}_{granularity}.csv')
File "/src/preparation/data_source.py", line 27, in _load_data
data = pd.read_sql(query, conn, parse_dates=['install_date'])
File "/usr/local/lib/python3.7/site-packages/modin/pandas/io.py", line 324, in read_sql
return DataFrame(query_compiler=BaseFactory.read_sql(**kwargs))
File "/usr/local/lib/python3.7/site-packages/modin/data_management/factories.py", line 155, in read_sql
return cls._determine_engine()._read_sql(**kwargs)
File "/usr/local/lib/python3.7/site-packages/modin/data_management/factories.py", line 159, in _read_sql
return cls.io_cls.read_sql(**kwargs)
File "/usr/local/lib/python3.7/site-packages/modin/engines/ray/generic/io.py", line 805, in read_sql
num_return_vals=num_parts + 1,
File "/usr/local/lib/python3.7/site-packages/ray/remote_function.py", line 152, in _remote
return invocation(args, kwargs)
File "/usr/local/lib/python3.7/site-packages/ray/remote_function.py", line 143, in invocation
resources=resources)
File "/usr/local/lib/python3.7/site-packages/ray/worker.py", line 636, in submit_task
args_for_raylet.append(put(arg))
File "/usr/local/lib/python3.7/site-packages/ray/worker.py", line 2227, in put
worker.put_object(object_id, value)
File "/usr/local/lib/python3.7/site-packages/ray/worker.py", line 403, in put_object
self.store_and_register(object_id, value)
File "/usr/local/lib/python3.7/site-packages/ray/worker.py", line 317, in store_and_register
self.task_driver_id))
File "/usr/local/lib/python3.7/site-packages/ray/utils.py", line 475, in _wrapper
return orig_attr(*args, **kwargs)
File "pyarrow/_plasma.pyx", line 496, in pyarrow._plasma.PlasmaClient.put
File "pyarrow/serialization.pxi", line 355, in pyarrow.lib.serialize
File "pyarrow/serialization.pxi", line 150, in pyarrow.lib.SerializationContext._serialize_callback
File "/usr/local/lib/python3.7/site-packages/ray/cloudpickle/cloudpickle.py", line 952, in dumps
cp.dump(obj)
File "/usr/local/lib/python3.7/site-packages/ray/cloudpickle/cloudpickle.py", line 267, in dump
return Pickler.dump(self, obj)
File "/usr/local/lib/python3.7/pickle.py", line 437, in dump
self.save(obj)
File "/usr/local/lib/python3.7/pickle.py", line 549, in save
self.save_reduce(obj=obj, *rv)
File "/usr/local/lib/python3.7/pickle.py", line 662, in save_reduce
save(state)
File "/usr/local/lib/python3.7/pickle.py", line 504, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/local/lib/python3.7/pickle.py", line 856, in save_dict
self._batch_setitems(obj.items())
File "/usr/local/lib/python3.7/pickle.py", line 882, in _batch_setitems
save(v)
File "/usr/local/lib/python3.7/pickle.py", line 549, in save
self.save_reduce(obj=obj, *rv)
File "/usr/local/lib/python3.7/pickle.py", line 662, in save_reduce
save(state)
File "/usr/local/lib/python3.7/pickle.py", line 504, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/local/lib/python3.7/pickle.py", line 856, in save_dict
self._batch_setitems(obj.items())
File "/usr/local/lib/python3.7/pickle.py", line 882, in _batch_setitems
save(v)
File "/usr/local/lib/python3.7/pickle.py", line 549, in save
self.save_reduce(obj=obj, *rv)
File "/usr/local/lib/python3.7/pickle.py", line 662, in save_reduce
save(state)
File "/usr/local/lib/python3.7/pickle.py", line 504, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/local/lib/python3.7/pickle.py", line 856, in save_dict
self._batch_setitems(obj.items())
File "/usr/local/lib/python3.7/pickle.py", line 882, in _batch_setitems
save(v)
File "/usr/local/lib/python3.7/pickle.py", line 524, in save
rv = reduce(self.proto)
TypeError: can't pickle _thread._local objects

# Fixed code
def read_sql(cls, sql, con, index_col=None, **kwargs):
    """Reads a SQL query or database table into a DataFrame.
    Args:
        sql: string or SQLAlchemy Selectable (select or text object) SQL query to be
            executed or a table name.
        con: SQLAlchemy connectable (engine/connection) or database string URI or
            DBAPI2 connection (fallback mode)
        index_col: Column(s) to set as index(MultiIndex).
        kwargs: Pass into pandas.read_sql function.
    """
    if cls.read_sql_remote_task is None:
        return super(RayIO, cls).read_sql(sql, con, index_col=index_col, **kwargs)

    import sqlalchemy as sa

    # In the case that we are given a SQLAlchemy Connection or Engine, the objects
    # are not pickleable. We have to convert it to the URL string and connect from
    # each of the workers.
    if isinstance(con, (sa.engine.Engine, sa.engine.Connection)):
        con = repr(con.engine.url)
    row_cnt_query = "SELECT COUNT(*) FROM ({}) as foo".format(sql)
    row_cnt = pandas.read_sql(row_cnt_query, con).squeeze()
    cols_names_df = pandas.read_sql(
        "SELECT * FROM ({}) as foo LIMIT 0".format(sql), con, index_col=index_col
    )
    cols_names = cols_names_df.columns
    num_parts = cls.frame_mgr_cls._compute_num_partitions()
    partition_ids = []
    index_ids = []
    limit = math.ceil(row_cnt / num_parts)
    for part in range(num_parts):
        offset = part * limit
        query = "SELECT * FROM ({}) as foo LIMIT {} OFFSET {}".format(
            sql, limit, offset
        )
        partition_id = cls.read_sql_remote_task._remote(
            args=(num_parts, query, con, index_col, kwargs),
            num_return_vals=num_parts + 1,
        )
        partition_ids.append(
            [cls.frame_partition_cls(obj) for obj in partition_id[:-1]]
        )
        index_ids.append(partition_id[-1])

    if index_col is None:  # sum all lens returned from partitions
        index_lens = ray.get(index_ids)
        new_index = pandas.RangeIndex(sum(index_lens))
    else:  # concat index returned from partitions
        index_lst = [x for part_index in ray.get(index_ids) for x in part_index]
        new_index = pandas.Index(index_lst).set_names(index_col)

    new_query_compiler = cls.query_compiler_cls(
        cls.frame_mgr_cls(np.array(partition_ids)), new_index, cols_names
    )
    return new_query_compiler