We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
tools.environment_append raises if tries to unset variable which was never set
after #4224, I may use the following code, for instance, to ensure variable is not set:
```
with environment_append({'CONAN_BASH_PATH': None}):
    pass
```
however, it raises if `CONAN_BASH_PATH` is not set (prior to the environment_append invocation):
```
Traceback (most recent call last):
  File "C:\bincrafters\conan\conans\test\unittests\client\tools\os_info\osinfo_test.py", line 39, in test_windows
    with environment_append(new_env):
  File "c:\users\sse4\appdata\local\programs\python\python36\lib\contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "C:\bincrafters\conan\conans\client\tools\env.py", line 57, in environment_append
    os.environ.pop(var)
  File "c:\users\sse4\appdata\local\programs\python\python36\lib\_collections_abc.py", line 795, in pop
    value = self[key]
  File "c:\users\sse4\appdata\local\programs\python\python36\lib\os.py", line 669, in __getitem__
    raise KeyError(key) from None
KeyError: 'CONAN_BASH_PATH'
```
I would expect `tools.environment_append` to be no op in such case, otherwise, it requires additional logic to workaround this behavior.

To help us debug your issue please explain:

- [ ] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [ ] I've specified the Conan version, operating system version and any tool that can be relevant.
- [ ] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `conans/client/tools/env.py`
Content:
```
1 import os
2 import sys
3 from contextlib import contextmanager
4 
5 from conans.client.run_environment import RunEnvironment
6 from conans.client.tools.files import _path_equals, which
7 from conans.errors import ConanException
8 
9 
10 @contextmanager
11 def pythonpath(conanfile):
12     python_path = conanfile.env.get("PYTHONPATH", None)
13     if python_path:
14         old_path = sys.path[:]
15         if isinstance(python_path, list):
16             sys.path.extend(python_path)
17         else:
18             sys.path.append(python_path)
19 
20         yield
21         sys.path = old_path
22     else:
23         yield
24 
25 
26 @contextmanager
27 def run_environment(conanfile):
28     with environment_append(RunEnvironment(conanfile).vars):
29         yield
30 
31 
32 @contextmanager
33 def environment_append(env_vars):
34     """
35     :param env_vars: List (dict) of simple environment vars. {name: value, name2: value2} => e.g.: MYVAR=1
36                      The values can also be lists of appendable environment vars. {name: [value, value2]}
37                       => e.g. PATH=/path/1:/path/2
38                      If the value is set to None, then that environment variable is unset.
39     :return: None
40     """
41     unset_vars = []
42     for key in env_vars.keys():
43         if env_vars[key] is None:
44             unset_vars.append(key)
45     for var in unset_vars:
46         env_vars.pop(var, None)
47     for name, value in env_vars.items():
48         if isinstance(value, list):
49             env_vars[name] = os.pathsep.join(value)
50             old = os.environ.get(name)
51             if old:
52                 env_vars[name] += os.pathsep + old
53     if env_vars or unset_vars:
54         old_env = dict(os.environ)
55         os.environ.update(env_vars)
56         for var in unset_vars:
57             os.environ.pop(var)
58         try:
59             yield
60         finally:
61             os.environ.clear()
62             os.environ.update(old_env)
63     else:
64         yield
65 
66 
67 @contextmanager
68 def no_op():
69     yield
70 
71 
72 @contextmanager
73 def remove_from_path(command):
74     curpath = os.getenv("PATH")
75     first_it = True
76     for _ in range(30):
77         if not first_it:
78             with environment_append({"PATH": curpath}):
79                 the_command = which(command)
80         else:
81             the_command = which(command)
82             first_it = False
83 
84         if not the_command:
85             break
86         new_path = []
87         for entry in curpath.split(os.pathsep):
88             if not _path_equals(entry, os.path.dirname(the_command)):
89                 new_path.append(entry)
90 
91         curpath = os.pathsep.join(new_path)
92     else:
93         raise ConanException("Error in tools.remove_from_path!! couldn't remove the tool '%s' "
94                              "from the path after 30 attempts, still found in '%s' this is a "
95                              "Conan client bug, please open an issue at: "
96                              "https://github.com/conan-io/conan\n\nPATH=%s"
97                              % (command, the_command, os.getenv("PATH")))
98 
99     with environment_append({"PATH": curpath}):
100         yield
101
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/conans/client/tools/env.py b/conans/client/tools/env.py
--- a/conans/client/tools/env.py
+++ b/conans/client/tools/env.py
@@ -54,7 +54,7 @@
         old_env = dict(os.environ)
         os.environ.update(env_vars)
         for var in unset_vars:
-            os.environ.pop(var)
+            os.environ.pop(var, None)
         try:
             yield
         finally:

{"golden_diff": "diff --git a/conans/client/tools/env.py b/conans/client/tools/env.py\n--- a/conans/client/tools/env.py\n+++ b/conans/client/tools/env.py\n@@ -54,7 +54,7 @@\n         old_env = dict(os.environ)\n         os.environ.update(env_vars)\n         for var in unset_vars:\n-            os.environ.pop(var)\n+            os.environ.pop(var, None)\n         try:\n             yield\n         finally:\n", "issue": "tools.environment_append raises if tries to unset variable which was never set\nafter #4224, I may use the following code, for instance, to ensure variable is not set:\r\n```\r\nwith environment_append({'CONAN_BASH_PATH': None}):\r\n    pass\r\n```\r\nhowever, it raises if `CONAN_BASH_PATH` is not set (prior to the environment_append invocation):\r\n```\r\nTraceback (most recent call last):\r\n  File \"C:\\bincrafters\\conan\\conans\\test\\unittests\\client\\tools\\os_info\\osinfo_test.py\", line 39, in test_windows\r\n    with environment_append(new_env):\r\n  File \"c:\\users\\sse4\\appdata\\local\\programs\\python\\python36\\lib\\contextlib.py\", line 81, in __enter__\r\n    return next(self.gen)\r\n  File \"C:\\bincrafters\\conan\\conans\\client\\tools\\env.py\", line 57, in environment_append\r\n    os.environ.pop(var)\r\n  File \"c:\\users\\sse4\\appdata\\local\\programs\\python\\python36\\lib\\_collections_abc.py\", line 795, in pop\r\n    value = self[key]\r\n  File \"c:\\users\\sse4\\appdata\\local\\programs\\python\\python36\\lib\\os.py\", line 669, in __getitem__\r\n    raise KeyError(key) from None\r\nKeyError: 'CONAN_BASH_PATH'\r\n```\r\nI would expect `tools.environment_append` to be no op in such case, otherwise, it requires additional logic to workaround this behavior.\r\n\r\nTo help us debug your issue please explain:\r\n\r\n- [ ] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).\r\n- [ ] I've specified the Conan version, operating system version and any tool that can be relevant.\r\n- [ ] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.\r\n\r\n\n", "before_files": [{"content": "import os\nimport sys\nfrom contextlib import contextmanager\n\nfrom conans.client.run_environment import RunEnvironment\nfrom conans.client.tools.files import _path_equals, which\nfrom conans.errors import ConanException\n\n\n@contextmanager\ndef pythonpath(conanfile):\n    python_path = conanfile.env.get(\"PYTHONPATH\", None)\n    if python_path:\n        old_path = sys.path[:]\n        if isinstance(python_path, list):\n            sys.path.extend(python_path)\n        else:\n            sys.path.append(python_path)\n\n        yield\n        sys.path = old_path\n    else:\n        yield\n\n\n@contextmanager\ndef run_environment(conanfile):\n    with environment_append(RunEnvironment(conanfile).vars):\n        yield\n\n\n@contextmanager\ndef environment_append(env_vars):\n    \"\"\"\n    :param env_vars: List (dict) of simple environment vars. {name: value, name2: value2} => e.g.: MYVAR=1\n                     The values can also be lists of appendable environment vars. {name: [value, value2]}\n                      => e.g. PATH=/path/1:/path/2\n                     If the value is set to None, then that environment variable is unset.\n    :return: None\n    \"\"\"\n    unset_vars = []\n    for key in env_vars.keys():\n        if env_vars[key] is None:\n            unset_vars.append(key)\n    for var in unset_vars:\n        env_vars.pop(var, None)\n    for name, value in env_vars.items():\n        if isinstance(value, list):\n            env_vars[name] = os.pathsep.join(value)\n            old = os.environ.get(name)\n            if old:\n                env_vars[name] += os.pathsep + old\n    if env_vars or unset_vars:\n        old_env = dict(os.environ)\n        os.environ.update(env_vars)\n        for var in unset_vars:\n            os.environ.pop(var)\n        try:\n            yield\n        finally:\n            os.environ.clear()\n            os.environ.update(old_env)\n    else:\n        yield\n\n\n@contextmanager\ndef no_op():\n    yield\n\n\n@contextmanager\ndef remove_from_path(command):\n    curpath = os.getenv(\"PATH\")\n    first_it = True\n    for _ in range(30):\n        if not first_it:\n            with environment_append({\"PATH\": curpath}):\n                the_command = which(command)\n        else:\n            the_command = which(command)\n            first_it = False\n\n        if not the_command:\n            break\n        new_path = []\n        for entry in curpath.split(os.pathsep):\n            if not _path_equals(entry, os.path.dirname(the_command)):\n                new_path.append(entry)\n\n        curpath = os.pathsep.join(new_path)\n    else:\n        raise ConanException(\"Error in tools.remove_from_path!! couldn't remove the tool '%s' \"\n                             \"from the path after 30 attempts, still found in '%s' this is a \"\n                             \"Conan client bug, please open an issue at: \"\n                             \"https://github.com/conan-io/conan\\n\\nPATH=%s\"\n                             % (command, the_command, os.getenv(\"PATH\")))\n\n    with environment_append({\"PATH\": curpath}):\n        yield\n", "path": "conans/client/tools/env.py"}], "after_files": [{"content": "import os\nimport sys\nfrom contextlib import contextmanager\n\nfrom conans.client.run_environment import RunEnvironment\nfrom conans.client.tools.files import _path_equals, which\nfrom conans.errors import ConanException\n\n\n@contextmanager\ndef pythonpath(conanfile):\n    python_path = conanfile.env.get(\"PYTHONPATH\", None)\n    if python_path:\n        old_path = sys.path[:]\n        if isinstance(python_path, list):\n            sys.path.extend(python_path)\n        else:\n            sys.path.append(python_path)\n\n        yield\n        sys.path = old_path\n    else:\n        yield\n\n\n@contextmanager\ndef run_environment(conanfile):\n    with environment_append(RunEnvironment(conanfile).vars):\n        yield\n\n\n@contextmanager\ndef environment_append(env_vars):\n    \"\"\"\n    :param env_vars: List (dict) of simple environment vars. {name: value, name2: value2} => e.g.: MYVAR=1\n                     The values can also be lists of appendable environment vars. {name: [value, value2]}\n                      => e.g. PATH=/path/1:/path/2\n                     If the value is set to None, then that environment variable is unset.\n    :return: None\n    \"\"\"\n    unset_vars = []\n    for key in env_vars.keys():\n        if env_vars[key] is None:\n            unset_vars.append(key)\n    for var in unset_vars:\n        env_vars.pop(var, None)\n    for name, value in env_vars.items():\n        if isinstance(value, list):\n            env_vars[name] = os.pathsep.join(value)\n            old = os.environ.get(name)\n            if old:\n                env_vars[name] += os.pathsep + old\n    if env_vars or unset_vars:\n        old_env = dict(os.environ)\n        os.environ.update(env_vars)\n        for var in unset_vars:\n            os.environ.pop(var, None)\n        try:\n            yield\n        finally:\n            os.environ.clear()\n            os.environ.update(old_env)\n    else:\n        yield\n\n\n@contextmanager\ndef no_op():\n    yield\n\n\n@contextmanager\ndef remove_from_path(command):\n    curpath = os.getenv(\"PATH\")\n    first_it = True\n    for _ in range(30):\n        if not first_it:\n            with environment_append({\"PATH\": curpath}):\n                the_command = which(command)\n        else:\n            the_command = which(command)\n            first_it = False\n\n        if not the_command:\n            break\n        new_path = []\n        for entry in curpath.split(os.pathsep):\n            if not _path_equals(entry, os.path.dirname(the_command)):\n                new_path.append(entry)\n\n        curpath = os.pathsep.join(new_path)\n    else:\n        raise ConanException(\"Error in tools.remove_from_path!! couldn't remove the tool '%s' \"\n                             \"from the path after 30 attempts, still found in '%s' this is a \"\n                             \"Conan client bug, please open an issue at: \"\n                             \"https://github.com/conan-io/conan\\n\\nPATH=%s\"\n                             % (command, the_command, os.getenv(\"PATH\")))\n\n    with environment_append({\"PATH\": curpath}):\n        yield\n", "path": "conans/client/tools/env.py"}]}