Repository: Cog-Creators/Red-DiscordBot

Issue:
[Core] DataManager/Bundled_Data_Path cannot be used in __init__
# Other bugs

<!-- 
Did you find a bug with something other than a command? Fill out the following:
-->

#### What were you trying to do?

have bundled_data_path in a variable initialized in `__init__`

#### What were you expecting to happen?

Successful initialization of the variable with the data path

#### What actually happened?

Init occurred faster than the bundled data folder creation, causing the cog to fail with a "dir could not be found"

#### How can we reproduce this issue?

Put the following in any cog that has data and try loading the cog:

```
def __init__(self):
self.path = bundled_data_path(self)
```




Discussion:
I cannot replicate this, please check the docs regarding this to ensure you remembered to initialize bundled data properly in your setup function prior to cog object creation.
A setup function like this:
```python
def setup(bot):
    cog = MyCog(bot)
    data_manager.load_bundled_data(cog, __file__)
    bot.add_cog(cog)
```
That would run the `__init__` of `MyCog` before the `load_bundled_data` has been run.
This is an ordering issue, not really a bug.
As a solution I'd suggest adding a note in the docs of `bundled_data_path` saying that `load_bundled_data` must be called before calling `bundled_data_path`, and perhaps that will clear things up. You still won't be able to call it in `__init__`, but a workaround is fairly easy:
```py
def setup(bot):
    cog = MyCog(bot)
    data_manager.load_bundled_data(cog, __file__)
    cog.function_which_calls_bundled_data_path()
    bot.add_cog(cog)
```
I disagree with this being solely a docs issue, but also one of usability. It makes sense that a cog would possibly need some of this data at init, and we could accommodate it similarly to config, by adding an optional keyword only parameter for the cog name.

Resolving patch:
diff --git a/redbot/core/data_manager.py b/redbot/core/data_manager.py
--- a/redbot/core/data_manager.py
+++ b/redbot/core/data_manager.py
@@ -1,15 +1,15 @@
-import sys
+import inspect
+import logging
 import os
-from pathlib import Path
-from typing import List
+import sys
+import tempfile
 from copy import deepcopy
-import hashlib
-import shutil
-import logging
+from pathlib import Path
 
 import appdirs
-import tempfile
+from discord.utils import deprecated
 
+from . import commands
 from .json_io import JsonIO
 
 __all__ = [
@@ -153,124 +153,28 @@ def core_data_path() -> Path:
     return core_path.resolve()
 
 
-def _find_data_files(init_location: str) -> (Path, List[Path]):
-    """
-    Discovers all files in the bundled data folder of an installed cog.
-
-    Parameters
-    ----------
-    init_location
-
-    Returns
-    -------
-    (pathlib.Path, list of pathlib.Path)
-    """
-    init_file = Path(init_location)
-    if not init_file.is_file():
-        return []
-
-    package_folder = init_file.parent.resolve() / "data"
-    if not package_folder.is_dir():
-        return []
-
-    all_files = list(package_folder.rglob("*"))
-
-    return package_folder, [p.resolve() for p in all_files if p.is_file()]
-
-
-def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: Path):
-    """
-    Filters out files from ``to_copy`` that already exist, and are the
-    same, in ``data_dir``. The files that are different are copied into
-    ``data_dir``.
-
-    Parameters
-    ----------
-    to_copy : list of pathlib.Path
-    bundled_data_dir : pathlib.Path
-    cog_data_dir : pathlib.Path
-    """
-
-    def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
-        for block in bytesiter:
-            hasher.update(block)
-        return hasher.hexdigest() if ashexstr else hasher.digest()
-
-    def file_as_blockiter(afile, blocksize=65536):
-        with afile:
-            block = afile.read(blocksize)
-            while len(block) > 0:
-                yield block
-                block = afile.read(blocksize)
-
-    lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir)) for p in to_copy}
-
-    for orig, poss_existing in lookup.items():
-        if not poss_existing.is_file():
-            poss_existing.parent.mkdir(exist_ok=True, parents=True)
-            exists_checksum = None
-        else:
-            exists_checksum = hash_bytestr_iter(
-                file_as_blockiter(poss_existing.open("rb")), hashlib.sha256()
-            )
-
-        orig_checksum = ...
-        if exists_checksum is not None:
-            orig_checksum = hash_bytestr_iter(file_as_blockiter(orig.open("rb")), hashlib.sha256())
-
-        if exists_checksum != orig_checksum:
-            shutil.copy(str(orig), str(poss_existing))
-            log.debug("Copying {} to {}".format(orig, poss_existing))
-
-
+# noinspection PyUnusedLocal
+@deprecated("bundled_data_path() without calling this function")
 def load_bundled_data(cog_instance, init_location: str):
-    """
-    This function copies (and overwrites) data from the ``data/`` folder
-    of the installed cog.
+    pass
 
-    .. important::
 
-        This function MUST be called from the ``setup()`` function of your
-        cog.
-
-    Examples
-    --------
-    >>> from redbot.core import data_manager
-    >>>
-    >>> def setup(bot):
-    >>>     cog = MyCog()
-    >>>     data_manager.load_bundled_data(cog, __file__)
-    >>>     bot.add_cog(cog)
-
-    Parameters
-    ----------
-    cog_instance
-        An instance of your cog class.
-    init_location : str
-        The ``__file__`` attribute of the file where your ``setup()``
-        function exists.
+def bundled_data_path(cog_instance: commands.Cog) -> Path:
     """
-    bundled_data_folder, to_copy = _find_data_files(init_location)
-
-    cog_data_folder = cog_data_path(cog_instance) / "bundled_data"
+    Get the path to the "data" directory bundled with this cog.
 
-    _compare_and_copy(to_copy, bundled_data_folder, cog_data_folder)
-
-
-def bundled_data_path(cog_instance) -> Path:
-    """
-    The "data" directory that has been copied from installed cogs.
+    The bundled data folder must be located alongside the ``.py`` file
+    which contains the cog class.
 
     .. important::
 
-        You should *NEVER* write to this directory. Data manager will
-        overwrite files in this directory each time `load_bundled_data`
-        is called. You should instead write to the directory provided by
-        `cog_data_path`.
+        You should *NEVER* write to this directory.
 
     Parameters
     ----------
     cog_instance
+        An instance of your cog. If calling from a command or method of
+        your cog, this should be ``self``.
 
     Returns
     -------
@@ -280,10 +184,10 @@ def bundled_data_path(cog_instance) -> Path:
     Raises
     ------
     FileNotFoundError
-        If no bundled data folder exists or if it hasn't been loaded yet.
-    """
+        If no bundled data folder exists.
 
-    bundled_path = cog_data_path(cog_instance) / "bundled_data"
+    """
+    bundled_path = Path(inspect.getfile(cog_instance.__class__)).parent / "data"
 
     if not bundled_path.is_dir():
         raise FileNotFoundError("No such directory {}".format(bundled_path))


Test patch: