Repository: Giskard-AI/giskard-oss
Issue: Giskard hub running tests: ModuleNotFoundError: No module named 'src'
Root cause: Architectural Constraint

## Checklist

<!-- Mark with an `x` all the checkboxes that apply (like `[x]`) -->

- [x] I've searched the project's [`issues`](https://github.com/Giskard-AI/giskard/issues?q=is%3Aissue).

## ❓ Question

Hello, I have a directory structure like the following.
This is actually a part of [streamlit](https://streamlit.io/) app
.
├── giskard_tests.py
├── pages
├── requirements.txt
├── src
│   ├── \__init__.py
│   ├── assets
│   ├── components
│   ├── evaluation
│   ├── guards
│   ├── modules
│   ├── observability
│   ├── prompt
│   └── utils
|── tests
│   ├── \__init__.py
── 🏠_Home.py


In my giskard_test file, i have model_predict defined and I'm able to run it from terminal (`python giskard_test.py`). I also have the code for uploading test_suites to giskard hub in this same file.
I'm able to see the test suites in giskard hub once this file is run. 

Only problem is that I'm not able to run the test suite back from giskard hub!
I have the worker connected successfully and am able to see it in settings

It always says that
`ModuleNotFoundError: No module named 'src'`
I've been trying to resolve this issue for one whole day!
Any help will be appreciated!


1. How can I fix this issue?
2. How is the folder structure handled

I'm not sure if this issue has to do with the directory structure.
I'ave added  \__init__.py to use as modules.
What is the recommended folder structure to try out for giskard? Where should the giskard file be ideally placed?
if kept in tests folder (As I'd personally like to), then how do i run the file from terminal? 

Note that in my giskard_tests file, i am importing functions from my src folder.

## 📎 Additional context

<!-- Add any other context or screenshots about the feature request here. -->

Logs from test suite run:
```
2024-03-18 17:03:46,686 pid:47224 MainThread giskard.utils.worker_pool INFO     Configured logger
2024-03-18 17:03:46,688 pid:47224 MainThread giskard.utils.worker_pool INFO     Start job execution a2b122a7-3ef8-46fa-9cef-beaf1b48e763
2024-03-18 17:03:47,206 pid:47224 MainThread giskard.client.giskard_client INFO     Artifact datasets/8f9c1a76-9294-4f43-945f-8df74d3f5a3c already exists, skipping download
2024-03-18 17:03:47,392 pid:47224 MainThread giskard.datasets.base INFO     Casting dataframe columns from {'question': 'object'} to {'question': 'object'}
2024-03-18 17:03:47,400 pid:47224 MainThread giskard.datasets.base INFO     Your 'pandas.DataFrame' is successfully wrapped by Giskard's 'Dataset' wrapper class.
2024-03-18 17:03:47,724 pid:47224 MainThread giskard.client.giskard_client INFO     Artifact datasets/8ddc9f91-97e1-42b2-958b-04d8676930c6 already exists, skipping download
2024-03-18 17:03:47,877 pid:47224 MainThread giskard.datasets.base INFO     Casting dataframe columns from {'question': 'object'} to {'question': 'object'}
2024-03-18 17:03:47,879 pid:47224 MainThread giskard.datasets.base INFO     Your 'pandas.DataFrame' is successfully wrapped by Giskard's 'Dataset' wrapper class.
2024-03-18 17:03:47,879 pid:47224 MainThread giskard.client.giskard_client INFO     Artifact datasets/ea2fd5b0-b861-4a1f-85dd-112e9e21fe3c already exists, skipping download
2024-03-18 17:03:48,056 pid:47224 MainThread giskard.datasets.base INFO     Casting dataframe columns from {'question': 'object'} to {'question': 'object'}
2024-03-18 17:03:48,058 pid:47224 MainThread giskard.datasets.base INFO     Your 'pandas.DataFrame' is successfully wrapped by Giskard's 'Dataset' wrapper class.
2024-03-18 17:03:48,059 pid:47224 MainThread giskard.client.giskard_client INFO     Artifact models/44f62372-1d6b-446c-b0b2-3b7f6898961a already exists, skipping download
2024-03-18 17:03:48,231 pid:47224 MainThread giskard.ml_worker.websocket.listener ERROR    An error occurred during the test suite execution: Failed to load 'PredictionFunctionModel' due to ModuleNotFoundError.
Make sure you are loading it in the environment with matched dependencies.
Traceback (most recent call last):
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/models/base/serialization.py", line 63, in load_model
    model = cloudpickle.load(f)
            ^^^^^^^^^^^^^^^^^^^
ModuleNotFoundError: No module named 'src'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/ml_worker/websocket/listener.py", line 545, in run_test_suite
    global_arguments = parse_function_arguments(client, params.globalArguments, loaded_artifacts)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/ml_worker/websocket/utils.py", line 214, in parse_function_arguments
    arguments[arg.name] = _get_or_load(
                          ^^^^^^^^^^^^^
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/ml_worker/websocket/utils.py", line 179, in _get_or_load
    loaded_artifacts[type][uuid] = load_fn()
                                   ^^^^^^^^^
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/ml_worker/websocket/utils.py", line 218, in <lambda>
    lambda: BaseModel.download(client, arg.model.project_key, arg.model.id),
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/models/base/model.py", line 519, in download
    model = clazz.load(local_dir, model_py_ver=model_py_ver, **constructor_params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/models/base/wrapper.py", line 256, in load
    return cls(model=cls.load_model(local_dir, model_py_ver), **constructor_params)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/aldrinjenson/Projects/PDF-Chat/.venv/lib/python3.11/site-packages/giskard/models/base/serialization.py", line 65, in load_model
    raise python_env_exception_helper(cls.__name__, e, required_py_ver=model_py_ver)
giskard.exceptions.giskard_exception.GiskardPythonDepException: Failed to load 'PredictionFunctionModel' due to ModuleNotFoundError.
Make sure you are loading it in the environment with matched dependencies.
2024-03-18 17:03:48,522 pid:47224 MainThread giskard.utils.worker_pool INFO     Finished job execution a2b122a7-3ef8-46fa-9cef-beaf1b48e763
```


Hello @aldrinjenson 

This is due to how giskard is currently serializing models. It uses cloudpickle to serializes all the code inside of the model module, and assumes that other modules will be dependencies of your python env. 

Since `giskard_tests.py` is outside of any module, then it won't serialize any code inside of `src` module. You should adapt a proper module structure to ensure that everything is automatically serialized (see [Structuring Your Project](https://docs.python-guide.org/writing/structure/#the-actual-module)): 

.
├── pages
├── requirements.txt
├── giskard_tests **# Rename src into your module name** 
|├── giskard_tests.py **# Move giskard_tests.py inside your module**
│ ├── _init_.py
│ ├── assets
│ ├── components
│ ├── evaluation
│ ├── guards
│ ├── modules
│ ├── observability
│ ├── prompt
│ └── utils
|── tests
│ ├── _init_.py
── 🏠_Home.py

Note that you can enfore serializing any module by using the following piece of code (but it's not recommended):

```python
import src
import cloudpickle

cloudpickle.register_pickle_by_value(src)

model = giskard.model(...)
model.save(...)
```