Repository: OpenMined/PySyft
Pull request: Add syft helper for handholding/messages
## Description
Adding a syft.helper based on issue #6921  

## Affected Dependencies
List any dependencies that are required for this change.

## How has this been tested?
- Describe the tests that you ran to verify your changes.
- Provide instructions so we can reproduce.
- List any relevant details for your test configuration.

## Checklist
- [ ] I have followed the [Contribution Guidelines](https://github.com/OpenMined/.github/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/OpenMined/.github/blob/master/CODE_OF_CONDUCT.md)
- [ ] I have commented my code following the [OpenMined Styleguide](https://github.com/OpenMined/.github/blob/master/STYLEGUIDE.md)
- [ ] I have labeled this PR with the relevant [Type labels](https://github.com/OpenMined/.github/labels?q=Type%3A)
- [ ] My changes are covered by tests


Problem statement:
UX Improvement: More hand-holding. When creating a Syft Tensor, tell DS they can annotate. After annotate, tell DS they can load_dataset
It isn't obvious how a new PySyft user would know they had to call `.private` or `.annotate` on their Syft Tensor. Perhaps we could do some signposting and when a new Syft Tensor is created, we could print something like "Syft Tensor created! To activate Differential Privacy protection, please call `.annotate` and load into a domain node!"

Hints from issue discussion:
I like this idea. I think this and other queries might be best turned on/off by a syft.helper=True flag (which could default to True)
This was actually opened by @IshanMi 
We could also persist this `syft settings` to their `~` home directory the same way we do with `hagrid`.
> We could also persist this `syft settings` to their `~` home directory the same way we do with `hagrid`.

Let's make this bit stretch for 0.7
Lets not do "stretch" anything, its either in or out and for this lets say its out:
https://github.com/OpenMined/Heartbeat/issues/1038

Any ambiguity should always be `Modulo` sliced off and put into another ticket.
I have had to disable this for now because every time a tensor gets created with internal code it prints. For SMPC this can result in hundreds of the same message getting printed.

https://github.com/OpenMined/PySyft/pull/7086

Let's create a new PR which enables this but does not trigger. Perhaps we could have a global timeout or something similar to prevent it from flooding prints?
Relying on a syft.helper_messages = True flag seems like a reasonable middle ground. Default it to False and set it to True in all our tutorials.

Commit message: added python-dotenv to setup.cfg

Patch to review:
diff --git a/notebooks/Experimental/Ishan/HelperMessages.ipynb b/notebooks/Experimental/Ishan/HelperMessages.ipynb
new file mode 100644
index 00000000000..474b7432a05
--- /dev/null
+++ b/notebooks/Experimental/Ishan/HelperMessages.ipynb
@@ -0,0 +1,129 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "a796c05c-dbbc-4883-b6a7-6da5af6c901c",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/Users/bitsofsteve/miniconda3/envs/syft/lib/python3.10/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
+      "  from .autonotebook import tqdm as notebook_tqdm\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Tutorial Mode:  True\n",
+      "<class 'bool'>\n"
+     ]
+    }
+   ],
+   "source": [
+    "import syft as sy"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "0443ee0c-03b1-4efb-8fd7-5cec073b05d4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "4ee5a936-4554-4cba-ab58-3e90985f1fa9",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Tensor annotated with DP Metadata!\n",
+      "You can upload this Tensor to a domain node by calling `<domain_client>.load_dataset` and passing in this tensor as an asset.\n"
+     ]
+    }
+   ],
+   "source": [
+    "tensor = sy.Tensor(np.ones(10)).annotate_with_dp_metadata(lower_bound=0, upper_bound=2, data_subjects=\"Ishan\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bd8544cd-5453-4102-8ab8-7594145aea87",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sy.helper = True"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "402bdc81-cc62-4ace-af3b-017da18ad021",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sy.helper"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "760f1ad6-4d09-4d14-b2a7-51f2a72b380c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tensor = sy.Tensor(np.ones(10)).annotate_with_dp_metadata(lower_bound=0, upper_bound=2, data_subjects=\"Ishan\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "adf30d01-be68-4488-890a-6fbf3398fdc0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sy.Tensor(1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d73cdf1b-5333-4fb9-9b22-c61c2181c0b2",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.7"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg
index c5381baa842..ae94fd73711 100644
--- a/packages/syft/setup.cfg
+++ b/packages/syft/setup.cfg
@@ -55,6 +55,7 @@ syft =
     tqdm==4.64.0
     typing_extensions==4.1.1 # backport to older python 3
     pyzmq==23.2.1 # https://github.com/zeromq/pyzmq/issues/1764
+    python-dotenv==0.21.0
 
 install_requires =
     %(syft)s
diff --git a/packages/syft/src/syft/.env b/packages/syft/src/syft/.env
new file mode 100644
index 00000000000..7be97a54c35
--- /dev/null
+++ b/packages/syft/src/syft/.env
@@ -0,0 +1 @@
+SYFT_TUTORIAL_MODE=False
\ No newline at end of file
diff --git a/packages/syft/src/syft/__init__.py b/packages/syft/src/syft/__init__.py
index 79a6f701d7f..b52c7c6c43c 100644
--- a/packages/syft/src/syft/__init__.py
+++ b/packages/syft/src/syft/__init__.py
@@ -84,6 +84,8 @@
 from .lib import load  # noqa: F401
 from .lib import load_lib  # noqa: F401
 from .registry import NetworkRegistry  # noqa: F401
+from .user_settings import UserSettings  # noqa: F401
+from .user_settings import settings  # noqa: F401
 
 sys.path.append(str(Path(__file__)))
 
@@ -113,3 +115,8 @@ def new_getattr(name: str) -> Any:
 @module_property
 def _networks() -> NetworkRegistry:
     return NetworkRegistry()
+
+
+@module_property
+def _settings() -> UserSettings:
+    return settings
diff --git a/packages/syft/src/syft/core/tensor/ancestors.py b/packages/syft/src/syft/core/tensor/ancestors.py
index 99f338df670..f482642ddc8 100644
--- a/packages/syft/src/syft/core/tensor/ancestors.py
+++ b/packages/syft/src/syft/core/tensor/ancestors.py
@@ -17,6 +17,7 @@
 from numpy.typing import ArrayLike
 
 # relative
+from ...user_settings import settings
 from ..adp.data_subject_ledger import DataSubjectLedger
 from ..adp.data_subject_list import DataSubjectArray
 from .config import DEFAULT_FLOAT_NUMPY_TYPE
@@ -503,11 +504,12 @@ def annotate_with_dp_metadata(
         You may join here: https://slack.openmined.org/
         """
 
-        print("Tensor annotated with DP Metadata!")
-        print(
-            "You can upload this Tensor to a domain node by calling `<domain_client>.load_dataset` "
-            "and passing in this tensor as an asset."
-        )
+        if settings.helper is True:
+            print("Tensor annotated with DP Metadata!")
+            print(
+                "You can upload this Tensor to a domain node by calling `<domain_client>.load_dataset` "
+                "and passing in this tensor as an asset."
+            )
 
         return self.copy()._private(
             min_val=lower_bound,
diff --git a/packages/syft/src/syft/user_settings.py b/packages/syft/src/syft/user_settings.py
new file mode 100644
index 00000000000..3d77fdb3541
--- /dev/null
+++ b/packages/syft/src/syft/user_settings.py
@@ -0,0 +1,41 @@
+# future
+from __future__ import annotations
+
+# stdlib
+import os
+
+# third party
+from dotenv import load_dotenv
+
+# relative
+from .core.common.decorators import singleton
+
+# Get root path of project
+root_path = os.path.dirname(os.path.abspath(__file__))
+
+# get the .env file path
+env_path = os.path.join(root_path, ".env")
+
+# load the .env file
+load_dotenv(dotenv_path=env_path)
+
+# TODO: Add this to a file in ~/.syft and add some ENV overrides
+tutorial_mode = bool(os.environ.get("SYFT_TUTORIAL_MODE", False))
+
+
+@singleton
+class UserSettings:
+    def __init__(self) -> None:
+        pass
+
+    @property
+    def helper(self) -> bool:
+        return tutorial_mode
+
+    @helper.setter
+    def helper(self, value: bool) -> None:
+        global tutorial_mode
+        tutorial_mode = value
+
+
+settings = UserSettings()


Code review comments:
diff_hunk: @@ -55,6 +55,7 @@ syft =
     tqdm==4.64.0
     typing_extensions==4.1.1 # backport to older python 3
     pyzmq==23.2.1 # https://github.com/zeromq/pyzmq/issues/1764
+    python-dotenv==0.21.0
path: packages/syft/setup.cfg
text: @user1:
I think we want to remove this for now.